[MERGE] *: batch and cache views RPCs + refactoring
- RPCs to load fields_views, fields and filters are batched for optimization purposes. - Those data are now stored in a JS cache. - Views API has been refactored.
This commit is contained in:
@@ -493,21 +493,20 @@ var abstractReconciliationLine = Widget.extend({
|
||||
// field_manager
|
||||
var dataset = new data.DataSet(this, "account.account", self.context);
|
||||
dataset.ids = [];
|
||||
dataset.arch = {
|
||||
var fields_view = {};
|
||||
fields_view.arch = {
|
||||
attrs: { string: "Stéphanie de Monaco", version: "7.0", class: "oe_form_container o_form_container" },
|
||||
children: [],
|
||||
tag: "form"
|
||||
};
|
||||
|
||||
self.field_manager = new FormView (
|
||||
this, dataset, false, {
|
||||
this, dataset, fields_view, {
|
||||
initial_mode: 'edit',
|
||||
disable_autofocus: false,
|
||||
$buttons: $(),
|
||||
$pager: $()
|
||||
});
|
||||
|
||||
self.field_manager.load_form(dataset);
|
||||
self.field_manager.appendTo(document.createDocumentFragment()); // starts the FormView
|
||||
|
||||
// fields default properties
|
||||
var Default_field = function() {
|
||||
|
||||
@@ -9,7 +9,8 @@ var _t = core._t;
|
||||
Sidebar.include({
|
||||
init : function(){
|
||||
this._super.apply(this, arguments);
|
||||
if (this.getParent().view_type == "form"){
|
||||
var view = this.getParent();
|
||||
if (view.fields_view && view.fields_view.type === "form") {
|
||||
this.sections.splice(1, 0, { 'name' : 'files', 'label' : _t('Attachment(s)'), });
|
||||
this.items.files = [];
|
||||
}
|
||||
|
||||
@@ -27,8 +27,8 @@ FormView.include({
|
||||
});
|
||||
|
||||
FavoriteMenu.include({
|
||||
prepare_dropdown_menu: function (filters) {
|
||||
this._super(filters);
|
||||
start: function () {
|
||||
this._super();
|
||||
var am = this.findAncestor(function(a) {
|
||||
return a instanceof ActionManager;
|
||||
});
|
||||
|
||||
@@ -10,6 +10,7 @@ var config = require('web.config');
|
||||
var ControlPanelMixin = require('web.ControlPanelMixin');
|
||||
var core = require('web.core');
|
||||
var data = require('web.data');
|
||||
var data_manager = require('web.data_manager');
|
||||
var Dialog = require('web.Dialog');
|
||||
var framework = require('web.framework');
|
||||
var Model = require('web.Model');
|
||||
@@ -160,6 +161,7 @@ var ChatAction = Widget.extend(ControlPanelMixin, {
|
||||
init: function(parent, action, options) {
|
||||
this._super.apply(this, arguments);
|
||||
this.action_manager = parent;
|
||||
this.dataset = new data.DataSetSearch(this, 'mail.message');
|
||||
this.domain = [];
|
||||
this.action = action;
|
||||
this.options = options || {};
|
||||
@@ -170,7 +172,14 @@ var ChatAction = Widget.extend(ControlPanelMixin, {
|
||||
},
|
||||
|
||||
willStart: function () {
|
||||
return chat_manager.is_ready;
|
||||
var self = this;
|
||||
var view_id = this.action && this.action.search_view_id && this.action.search_view_id[0];
|
||||
var def = data_manager
|
||||
.load_fields_view(this.dataset, view_id, 'search', false)
|
||||
.then(function (fields_view) {
|
||||
self.fields_view = fields_view;
|
||||
});
|
||||
return $.when(this._super(), chat_manager.is_ready, def);
|
||||
},
|
||||
|
||||
start: function() {
|
||||
@@ -182,8 +191,6 @@ var ChatAction = Widget.extend(ControlPanelMixin, {
|
||||
action: this.action,
|
||||
disable_groupby: true,
|
||||
};
|
||||
var dataset = new data.DataSetSearch(this, 'mail.message');
|
||||
var view_id = (this.action && this.action.search_view_id && this.action.search_view_id[0]) || false;
|
||||
var default_channel_id = this.options.active_id ||
|
||||
this.action.context.active_id ||
|
||||
this.action.params.default_active_id ||
|
||||
@@ -191,7 +198,7 @@ var ChatAction = Widget.extend(ControlPanelMixin, {
|
||||
var default_channel = chat_manager.get_channel(default_channel_id) ||
|
||||
chat_manager.get_channel('channel_inbox');
|
||||
|
||||
this.searchview = new SearchView(this, dataset, view_id, {}, options);
|
||||
this.searchview = new SearchView(this, this.dataset, this.fields_view, options);
|
||||
this.searchview.on('search_data', this, this.on_search);
|
||||
|
||||
this.basic_composer = new composer.BasicComposer(this, {mention_partners_restricted: true});
|
||||
|
||||
@@ -15,7 +15,6 @@ var _lt = core._lt;
|
||||
var SalesTeamDashboardView = KanbanView.extend({
|
||||
display_name: _lt('Dashboard'),
|
||||
icon: 'fa-dashboard',
|
||||
view_type: "sales_team_dashboard",
|
||||
searchview_hidden: true,
|
||||
events: {
|
||||
'click .o_dashboard_action': 'on_dashboard_action_clicked',
|
||||
|
||||
@@ -1103,7 +1103,7 @@ class Binary(http.Controller):
|
||||
class Action(http.Controller):
|
||||
|
||||
@http.route('/web/action/load', type='json', auth="user")
|
||||
def load(self, action_id, do_not_eval=False, additional_context=None):
|
||||
def load(self, action_id, additional_context=None):
|
||||
Actions = request.session.model('ir.actions.actions')
|
||||
value = False
|
||||
try:
|
||||
|
||||
@@ -5,6 +5,7 @@ var ControlPanel = require('web.ControlPanel');
|
||||
var core = require('web.core');
|
||||
var crash_manager = require('web.crash_manager');
|
||||
var data = require('web.data');
|
||||
var data_manager = require('web.data_manager');
|
||||
var Dialog = require('web.Dialog');
|
||||
var framework = require('web.framework');
|
||||
var pyeval = require('web.pyeval');
|
||||
@@ -564,7 +565,7 @@ var ActionManager = Widget.extend({
|
||||
active_ids : options.additional_context.active_ids,
|
||||
active_model : options.additional_context.active_model
|
||||
};
|
||||
return self.rpc("/web/action/load", { action_id: action, additional_context : additional_context }).then(function(result) {
|
||||
return data_manager.load_action(action, additional_context).then(function(result) {
|
||||
return self.do_action(result, options);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -186,8 +186,7 @@ function start_modules (modules) {
|
||||
// Monkey-patching of the ListView for backward compatibiliy of the colors and
|
||||
// fonts row's attributes, as they are deprecated in 9.0.
|
||||
ListView.include({
|
||||
load_list: function(data) {
|
||||
this._super(data);
|
||||
willStart: function() {
|
||||
if (this.fields_view.arch.attrs.colors) {
|
||||
this.colors = _(this.fields_view.arch.attrs.colors.split(';')).chain()
|
||||
.compact()
|
||||
@@ -208,6 +207,8 @@ ListView.include({
|
||||
return [font, py.parse(py.tokenize(expr)), expr];
|
||||
}).value();
|
||||
}
|
||||
|
||||
return this._super(data);
|
||||
},
|
||||
/**
|
||||
* Returns the style for the provided record in the current view (from the
|
||||
|
||||
@@ -0,0 +1,226 @@
|
||||
odoo.define('web.DataManager', function (require) {
|
||||
"use strict";
|
||||
|
||||
var core = require('web.core');
|
||||
var Model = require('web.DataModel');
|
||||
var session = require('web.session');
|
||||
var utils = require('web.utils');
|
||||
|
||||
return core.Class.extend({
|
||||
init: function () {
|
||||
this.Filters = new Model('ir.filters');
|
||||
this._cache = {
|
||||
actions: {},
|
||||
fields_views: {},
|
||||
fields: {},
|
||||
filters: {},
|
||||
views: {},
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Loads an action from its id or xmlid.
|
||||
*
|
||||
* @param {int|string} [action_id] the action id or xmlid
|
||||
* @param {Object} [additional_context] used to load the action
|
||||
* @return {Deferred} resolved with the action whose id or xmlid is action_id
|
||||
*/
|
||||
load_action: function (action_id, additional_context) {
|
||||
var self = this;
|
||||
var key = this._gen_key(action_id, additional_context || {});
|
||||
|
||||
if (!this._cache.actions[key]) {
|
||||
this._cache.actions[key] = session.rpc("/web/action/load", {
|
||||
action_id: action_id,
|
||||
additional_context : additional_context,
|
||||
}).then(function (action) {
|
||||
self._cache.actions[key] = action.no_cache ? null : self._cache.actions[key];
|
||||
return action;
|
||||
});
|
||||
}
|
||||
|
||||
return this._cache.actions[key];
|
||||
},
|
||||
|
||||
/**
|
||||
* Loads various information concerning views: fields_view for each view,
|
||||
* and optionally filters and fields.
|
||||
*
|
||||
* @param {Object} [dataset] the dataset for which the views are loaded
|
||||
* @param {Array} [views_descr] array of [view_id, view_type]
|
||||
* @param {Object} [options] dictionnary of various options:
|
||||
* - options.load_filters: whether or not to load the filters,
|
||||
* - options.action_id: the action_id (required to load filters),
|
||||
* - options.load_fields: whether or not to load the fields,
|
||||
* - options.toolbar: whether or not a toolbar will be displayed,
|
||||
* @return {Deferred} resolved with the requested views information
|
||||
*/
|
||||
load_views: function (dataset, views_descr, options) {
|
||||
var self = this;
|
||||
var model = dataset.model;
|
||||
var context = dataset.get_context();
|
||||
var key = this._gen_key(model, views_descr, options || {}, context);
|
||||
|
||||
if (!this._cache.views[key]) {
|
||||
// Don't load fields or filters if already in cache
|
||||
options.load_fields = options.load_fields && !this._cache.fields[model];
|
||||
var filters_key;
|
||||
if (options.load_filters) {
|
||||
filters_key = this._gen_key(model, options.action_id);
|
||||
options.load_filters = !this._cache.filters[filters_key];
|
||||
}
|
||||
|
||||
this._cache.views[key] = dataset.call('load_views', {
|
||||
views: views_descr,
|
||||
options: options,
|
||||
context: context,
|
||||
}).then(function (result) {
|
||||
// Postprocess fields_views and insert them into the fields_views cache
|
||||
result.fields_views = _.mapObject(result.fields_views, self._postprocess_fvg.bind(self)); // FIXME: lazy postprocess?
|
||||
_.each(views_descr, function (view_descr) {
|
||||
var toolbar = options.toolbar && view_descr[1] !== 'search';
|
||||
var fv_key = self._gen_key(model, view_descr[0], view_descr[1], toolbar, context);
|
||||
self._cache.fields_views[fv_key] = $.when(result.fields_views[view_descr[1]]);
|
||||
});
|
||||
|
||||
// Insert filters, if any, into the filters cache
|
||||
if (result.filters) {
|
||||
self._cache.filters[filters_key] = $.when(result.filters);
|
||||
}
|
||||
|
||||
// Insert fields, if any, into the fields cache
|
||||
if (result.fields) {
|
||||
self._cache.fields[model] = $.when(result.fields);
|
||||
}
|
||||
|
||||
return result.fields_views;
|
||||
});
|
||||
}
|
||||
|
||||
return this._cache.views[key];
|
||||
},
|
||||
|
||||
/**
|
||||
* Loads the fields_view of a given model, view_type and optional view_id.
|
||||
*
|
||||
* @param {Object} [dataset] the dataset for which the fields_view is loaded
|
||||
* @param {int} [view_id] the id of the view (optional)
|
||||
* @param {string} [view_type] the type of the view
|
||||
* @param {Boolean} [toolbar] whether or not a toolbar will be displayed
|
||||
* @return {Deferred} resolved with the requested fields_view
|
||||
*/
|
||||
load_fields_view: function (dataset, view_id, view_type, toolbar) {
|
||||
var key = this._gen_key(dataset.model, view_id, view_type, toolbar, dataset.get_context());
|
||||
if (!this._cache.fields_views[key]) {
|
||||
this._cache.fields_views[key] = dataset.call('fields_view_get', {
|
||||
view_id: view_id,
|
||||
view_type: view_type,
|
||||
toolbar: toolbar,
|
||||
context: dataset.get_context(),
|
||||
}).then(this._postprocess_fvg.bind(this));
|
||||
}
|
||||
return this._cache.fields_views[key];
|
||||
},
|
||||
|
||||
/**
|
||||
* Loads the fields of a given model.
|
||||
*
|
||||
* @param {Object} [dataset] the dataset for which the fields are loaded
|
||||
* @return {Deferred} resolved with the requested fields
|
||||
*/
|
||||
load_fields: function (dataset) {
|
||||
if (!this._cache.fields[dataset.model]) {
|
||||
this._cache.fields[dataset.model] = dataset.call('fields_get', {
|
||||
context: dataset.get_context(),
|
||||
});
|
||||
}
|
||||
return this._cache.fields[dataset.model];
|
||||
},
|
||||
|
||||
/**
|
||||
* Loads the filters of a given model and optional action id.
|
||||
*
|
||||
* @param {Object} [dataset] the dataset for which the filters are loaded
|
||||
* @param {int} [action_id] the id of the action (optional)
|
||||
* @return {Deferred} resolved with the requested filters
|
||||
*/
|
||||
load_filters: function (dataset, action_id) {
|
||||
var key = this._gen_key(dataset.model, action_id);
|
||||
if (!this._cache.filters[key]) {
|
||||
this._cache.filters[key] = this.Filters.call('get_filters', [dataset.model, action_id], {
|
||||
context: dataset.get_context(),
|
||||
});
|
||||
}
|
||||
return this._cache.filters[key];
|
||||
},
|
||||
|
||||
/**
|
||||
* Calls 'create_or_replace' on 'ir_filters'.
|
||||
*
|
||||
* @param {Object} [filter] the filter description
|
||||
* @return {Deferred} resolved with the id of the created or replaced filter
|
||||
*/
|
||||
create_filter: function (filter) {
|
||||
var self = this;
|
||||
return this.Filters
|
||||
.call('create_or_replace', [filter])
|
||||
.then(function (filter_id) {
|
||||
var key = [
|
||||
filter.model_id,
|
||||
filter.action_id || false,
|
||||
].join(',');
|
||||
self._cache.filters[key] = null; // invalidate cache
|
||||
return filter_id;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Calls 'unlink' on 'ir_filters'.
|
||||
*
|
||||
* @param {Object} [filter] the description of the filter to remove
|
||||
* @return {Deferred}
|
||||
*/
|
||||
delete_filter: function (filter) {
|
||||
var self = this;
|
||||
return this.Filters
|
||||
.call('unlink', [filter.id])
|
||||
.then(function () {
|
||||
self._cache.filters = {}; // invalidate cache
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Private function that postprocesses fields_view (mainly parses the arch attribute)
|
||||
*/
|
||||
_postprocess_fvg: function (fields_view) {
|
||||
var self = this;
|
||||
var doc = $.parseXML(fields_view.arch).documentElement;
|
||||
fields_view.arch = utils.xml_to_json(doc, (doc.nodeName.toLowerCase() !== 'kanban'));
|
||||
if ('id' in fields_view.fields) {
|
||||
// Special case for id's
|
||||
var id_field = fields_view.fields.id;
|
||||
id_field.original_type = id_field.type;
|
||||
id_field.type = 'id';
|
||||
}
|
||||
_.each(fields_view.fields, function(field) {
|
||||
_.each(field.views || {}, function(view) {
|
||||
self._postprocess_fvg(view);
|
||||
});
|
||||
});
|
||||
return fields_view;
|
||||
},
|
||||
|
||||
/**
|
||||
* Private function that generates a cache key from its arguments
|
||||
*/
|
||||
_gen_key: function () {
|
||||
return _.map(Array.prototype.slice.call(arguments), function (arg) {
|
||||
if (!arg) {
|
||||
return false;
|
||||
}
|
||||
return _.isObject(arg) ? JSON.stringify(arg) : arg;
|
||||
}).join(',');
|
||||
},
|
||||
});
|
||||
|
||||
});
|
||||
@@ -3,7 +3,6 @@ odoo.define('web.Model', function (require) {
|
||||
|
||||
var Class = require('web.Class');
|
||||
var session = require('web.session');
|
||||
var utils = require('web.utils');
|
||||
|
||||
var Model = Class.extend({
|
||||
/**
|
||||
@@ -70,41 +69,6 @@ var Model = Class.extend({
|
||||
signal: signal
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Performs a fields_view_get and apply postprocessing.
|
||||
* return a {$.Deferred} resolved with the fvg
|
||||
*
|
||||
* @param {Object} args
|
||||
* @param {String|Object} args.model instance.web.Model instance or string repr of the model
|
||||
* @param {Object} [args.context] context if args.model is a string
|
||||
* @param {Number} [args.view_id] id of the view to be loaded, default view if null
|
||||
* @param {String} [args.view_type] type of view to be loaded if view_id is null
|
||||
* @param {Boolean} [args.toolbar=false] get the toolbar definition
|
||||
*/
|
||||
fields_view_get: function (options) {
|
||||
|
||||
return this.call('fields_view_get', {
|
||||
view_id: options.view_id,
|
||||
view_type: options.view_type,
|
||||
context: options.context,
|
||||
toolbar: options.toolbar || false
|
||||
}).then(function postprocess(fvg) {
|
||||
var doc = $.parseXML(fvg.arch).documentElement;
|
||||
fvg.arch = utils.xml_to_json(doc, (doc.nodeName.toLowerCase() !== 'kanban'));
|
||||
if ('id' in fvg.fields) {
|
||||
// Special case for id's
|
||||
var id_field = fvg.fields.id;
|
||||
id_field.original_type = id_field.type;
|
||||
id_field.type = 'id';
|
||||
}
|
||||
_.each(fvg.fields, function(field) {
|
||||
_.each(field.views || {}, function(view) {
|
||||
postprocess(view);
|
||||
});
|
||||
});
|
||||
return fvg;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return Model;
|
||||
|
||||
@@ -3,6 +3,7 @@ odoo.define('web.View', function (require) {
|
||||
|
||||
var core = require('web.core');
|
||||
var data = require('web.data');
|
||||
var data_manager = require('web.data_manager');
|
||||
var pyeval = require('web.pyeval');
|
||||
var Widget = require('web.Widget');
|
||||
|
||||
@@ -14,10 +15,11 @@ var View = Widget.extend({
|
||||
this.do_action(action_data);
|
||||
}
|
||||
},
|
||||
defaults: {
|
||||
action: {},
|
||||
},
|
||||
// name displayed in view switchers
|
||||
display_name: '',
|
||||
// define a view type for each view to allow automatic call to fields_view_get.
|
||||
view_type: undefined,
|
||||
// used by views that need a searchview.
|
||||
searchable: true,
|
||||
// used by views that need a searchview but don't want it to be displayed.
|
||||
@@ -27,57 +29,28 @@ var View = Widget.extend({
|
||||
multi_record: true,
|
||||
// icon is the font-awesome icon to display in the view switcher
|
||||
icon: 'fa-question',
|
||||
init: function(parent, dataset, view_id, options) {
|
||||
// whether or not the view requires the model's fields to render itself
|
||||
require_fields: false,
|
||||
init: function(parent, dataset, fields_view, options) {
|
||||
this._super(parent);
|
||||
this.ViewManager = parent;
|
||||
this.dataset = dataset;
|
||||
this.view_id = view_id;
|
||||
this.set_default_options(options);
|
||||
this.model = dataset.model;
|
||||
this.fields_view = fields_view;
|
||||
this.options = _.defaults({}, options, this.defaults);
|
||||
},
|
||||
start: function () {
|
||||
return this.load_view();
|
||||
},
|
||||
load_view: function() {
|
||||
var self = this;
|
||||
var view_loaded_def;
|
||||
if (this.embedded_view) {
|
||||
view_loaded_def = $.Deferred();
|
||||
$.async_when().done(function() {
|
||||
view_loaded_def.resolve(self.embedded_view);
|
||||
});
|
||||
} else {
|
||||
if (! this.view_type)
|
||||
console.warn("view_type is not defined", this);
|
||||
view_loaded_def = this.dataset._model.fields_view_get({
|
||||
"view_id": this.view_id,
|
||||
"view_type": this.view_type,
|
||||
"toolbar": !!this.options.sidebar,
|
||||
"context": this.dataset.get_context(),
|
||||
});
|
||||
}
|
||||
return this.alive(view_loaded_def).then(function(r) {
|
||||
self.fields_view = r;
|
||||
// add css classes that reflect the (absence of) access rights
|
||||
self.$el.addClass('oe_view')
|
||||
.toggleClass('oe_cannot_create', !self.is_action_enabled('create'))
|
||||
.toggleClass('oe_cannot_edit', !self.is_action_enabled('edit'))
|
||||
.toggleClass('oe_cannot_delete', !self.is_action_enabled('delete'));
|
||||
return $.when(self.view_loading(r)).then(function() {
|
||||
self.trigger('view_loaded', r);
|
||||
});
|
||||
});
|
||||
},
|
||||
view_loading: function(r) {
|
||||
},
|
||||
set_default_options: function(options) {
|
||||
this.options = options || {};
|
||||
_.defaults(this.options, {
|
||||
// All possible views options should be defaulted here
|
||||
$sidebar: null,
|
||||
sidebar_id: null,
|
||||
action: null,
|
||||
action_views_ids: {}
|
||||
});
|
||||
/**
|
||||
* Triggers event 'view_loaded'.
|
||||
* Views extending start() must call this.super() once they are ready.
|
||||
* @return {Deferred}
|
||||
*/
|
||||
start: function() {
|
||||
// add css classes that reflect the (absence of) access rights
|
||||
this.$el.addClass('oe_view')
|
||||
.toggleClass('oe_cannot_create', !this.is_action_enabled('create'))
|
||||
.toggleClass('oe_cannot_edit', !this.is_action_enabled('edit'))
|
||||
.toggleClass('oe_cannot_delete', !this.is_action_enabled('delete'));
|
||||
return this._super().then(this.trigger.bind(this, 'view_loaded'));
|
||||
},
|
||||
/**
|
||||
* Fetches and executes the action identified by ``action_data``.
|
||||
@@ -87,7 +60,7 @@ var View = Widget.extend({
|
||||
* @param {String} [action_data.special=null] special action handlers (currently: only ``'cancel'``)
|
||||
* @param {String} [action_data.type='workflow'] the action type, if present, one of ``'object'``, ``'action'`` or ``'workflow'``
|
||||
* @param {Object} [action_data.context=null] additional action context, to add to the current context
|
||||
* @param {instance.web.DataSet} dataset a dataset object used to communicate with the server
|
||||
* @param {DataSet} dataset a dataset object used to communicate with the server
|
||||
* @param {Object} [record_id] the identifier of the object on which the action is to be applied
|
||||
* @param {Function} on_closed callback to execute when dialog is closed or when the action does not generate any result (no new action)
|
||||
*/
|
||||
@@ -150,56 +123,25 @@ var View = Widget.extend({
|
||||
core.bus.trigger('do_reload_needaction');
|
||||
});
|
||||
} else if (action_data.type=="action") {
|
||||
return this.rpc('/web/action/load', {
|
||||
action_id: action_data.name,
|
||||
context: _.extend(pyeval.eval('context', context), {'active_model': dataset.model, 'active_ids': dataset.ids, 'active_id': record_id}),
|
||||
do_not_eval: true
|
||||
}).then(handler);
|
||||
return data_manager.load_action(action_data.name, _.extend(pyeval.eval('context', context), {
|
||||
active_model: dataset.model,
|
||||
active_ids: dataset.ids,
|
||||
active_id: record_id
|
||||
})).then(handler);
|
||||
} else {
|
||||
return dataset.exec_workflow(record_id, action_data.name).then(handler);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Directly set a view to use instead of calling fields_view_get. This method must
|
||||
* be called before start(). When an embedded view is set, underlying implementations
|
||||
* of instance.web.View must use the provided view instead of any other one.
|
||||
*
|
||||
* @param embedded_view A view.
|
||||
*/
|
||||
set_embedded_view: function(embedded_view) {
|
||||
this.embedded_view = embedded_view;
|
||||
},
|
||||
do_show: function () {
|
||||
this._super();
|
||||
core.bus.trigger('view_shown', this);
|
||||
},
|
||||
is_active: function () {
|
||||
return this.ViewManager.active_view.controller === this;
|
||||
},
|
||||
/**
|
||||
* Wraps fn to only call it if the current view is the active one. If the
|
||||
* current view is not active, doesn't call fn.
|
||||
*
|
||||
* fn can not return anything, as a non-call to fn can't return anything
|
||||
* either
|
||||
*
|
||||
* @param {Function} fn function to wrap in the active guard
|
||||
*/
|
||||
guard_active: function (fn) {
|
||||
var self = this;
|
||||
return function () {
|
||||
if (self.is_active()) {
|
||||
fn.apply(self, arguments);
|
||||
}
|
||||
};
|
||||
},
|
||||
do_push_state: function(state) {
|
||||
if (this.getParent() && this.getParent().do_push_state) {
|
||||
this.getParent().do_push_state(state);
|
||||
}
|
||||
},
|
||||
do_load_state: function (state, warm) {
|
||||
|
||||
},
|
||||
/**
|
||||
* This function should render the buttons of the view, set this.$buttons to
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
odoo.define('web.data_manager', function (require) {
|
||||
"use strict";
|
||||
|
||||
var DataManager = require('web.DataManager');
|
||||
|
||||
var data_manager = new DataManager();
|
||||
|
||||
return data_manager;
|
||||
|
||||
});
|
||||
@@ -3,7 +3,7 @@ odoo.define('web.ViewManager', function (require) {
|
||||
|
||||
var ControlPanelMixin = require('web.ControlPanelMixin');
|
||||
var core = require('web.core');
|
||||
var data = require('web.data');
|
||||
var data_manager = require('web.data_manager');
|
||||
var framework = require('web.framework');
|
||||
var Model = require('web.DataModel');
|
||||
var pyeval = require('web.pyeval');
|
||||
@@ -17,14 +17,14 @@ var ViewManager = Widget.extend(ControlPanelMixin, {
|
||||
template: "ViewManager",
|
||||
/**
|
||||
* @param {Object} [dataset]
|
||||
* @param {Array} [views] List of [view_id, view_type]
|
||||
* @param {Array} [views] List of [view_id, view_type[, fields_view]]
|
||||
* @param {Object} [flags] various boolean describing UI state
|
||||
*/
|
||||
init: function(parent, dataset, views, flags, options) {
|
||||
var self = this;
|
||||
this._super(parent);
|
||||
|
||||
this.action = options && options.action;
|
||||
this.action = options && options.action || {};
|
||||
this.action_manager = options && options.action_manager;
|
||||
this.flags = flags || {};
|
||||
this.dataset = dataset;
|
||||
@@ -33,7 +33,7 @@ var ViewManager = Widget.extend(ControlPanelMixin, {
|
||||
this.view_stack = []; // used for breadcrumbs
|
||||
this.active_view = null;
|
||||
this.registry = core.view_registry;
|
||||
this.title = this.action && this.action.name;
|
||||
this.title = this.action.name;
|
||||
this.is_in_DOM = false; // used to know if the view manager is attached in the DOM
|
||||
_.each(views, function (view) {
|
||||
var view_type = view[1] || view.view_type;
|
||||
@@ -44,45 +44,42 @@ var ViewManager = Widget.extend(ControlPanelMixin, {
|
||||
}
|
||||
var view_label = View.prototype.display_name;
|
||||
var view_descr = {
|
||||
controller: null,
|
||||
options: view.options || {},
|
||||
view_id: view[0] || view.view_id,
|
||||
type: view_type,
|
||||
label: view_label,
|
||||
embedded_view: view.embedded_view,
|
||||
title: self.title,
|
||||
button_label: _.str.sprintf(_t('%(view_type)s view'), {'view_type': (view_label || view_type)}),
|
||||
multi_record: View.prototype.multi_record,
|
||||
accesskey: View.prototype.accesskey,
|
||||
icon: View.prototype.icon,
|
||||
};
|
||||
accesskey: View.prototype.accesskey,
|
||||
button_label: _.str.sprintf(_t('%(view_type)s view'), {'view_type': (view_label || view_type)}),
|
||||
controller: null,
|
||||
fields_view: view[2] || view.fields_view,
|
||||
embedded_view: view.embedded_view,
|
||||
icon: View.prototype.icon,
|
||||
label: view_label,
|
||||
multi_record: View.prototype.multi_record,
|
||||
options: view.options || {},
|
||||
require_fields: View.prototype.require_fields,
|
||||
title: self.title,
|
||||
type: view_type,
|
||||
view_id: view[0] || view.view_id,
|
||||
};
|
||||
self.view_order.push(view_descr);
|
||||
self.views[view_type] = view_descr;
|
||||
});
|
||||
this.first_view = this.views[options && options.view_type]; // view to open first
|
||||
|
||||
// Listen to event 'switch_view' indicating that the VM must now display view wiew_type
|
||||
this.on('switch_view', this, function(view_type) {
|
||||
if (view_type === 'form' && this.active_view && this.active_view.type === 'form') {
|
||||
this._display_view(view_type);
|
||||
} else {
|
||||
this.switch_mode(view_type);
|
||||
}
|
||||
});
|
||||
this.default_view = this.get_default_view();
|
||||
},
|
||||
willStart: function () {
|
||||
var views_def;
|
||||
var first_view_to_display = this.first_view || this.default_view;
|
||||
if (!first_view_to_display.fields_view || (this.flags.search_view && !this.search_fields_view)) {
|
||||
views_def = this.load_views(first_view_to_display.require_fields);
|
||||
}
|
||||
return $.when(this._super(), views_def);
|
||||
},
|
||||
/**
|
||||
* @returns {jQuery.Deferred} initial view loading promise
|
||||
* @return {Deferred} initial view and search view (if any) loading promise
|
||||
*/
|
||||
start: function() {
|
||||
var self = this;
|
||||
this._super();
|
||||
|
||||
var views_ids = {};
|
||||
_.each(this.views, function (view) {
|
||||
views_ids[view.type] = view.view_id;
|
||||
view.options = _.extend({
|
||||
action : self.action,
|
||||
action_views_ids : views_ids,
|
||||
action: self.action,
|
||||
}, self.flags, self.flags[view.type], view.options);
|
||||
view.$container = self.$(".oe-view-manager-view-" + view.type);
|
||||
});
|
||||
@@ -99,16 +96,49 @@ var ViewManager = Widget.extend(ControlPanelMixin, {
|
||||
|
||||
// If a non multi-record first_view is given, switch to it but first push the default_view
|
||||
// to the view_stack to complete the breadcrumbs
|
||||
var default_view = this.get_default_view();
|
||||
if (this.first_view && !this.first_view.multi_record && default_view.multi_record) {
|
||||
default_view.controller = this.create_view(default_view);
|
||||
this.view_stack.push(default_view);
|
||||
if (this.first_view && !this.first_view.multi_record && this.default_view.multi_record) {
|
||||
this.default_view.controller = this.create_view(this.default_view);
|
||||
this.view_stack.push(this.default_view);
|
||||
}
|
||||
var view_to_load = this.first_view || default_view;
|
||||
var view_to_load = this.first_view || this.default_view;
|
||||
var options = this.flags[view_to_load] && this.flags[view_to_load].options;
|
||||
var main_view_loaded = this.switch_mode(view_to_load.type, options);
|
||||
|
||||
return $.when(main_view_loaded, this.search_view_loaded);
|
||||
return $.when(this._super(), main_view_loaded, this.search_view_loaded);
|
||||
},
|
||||
/**
|
||||
* Loads all missing field_views of views in this.views and the search view.
|
||||
*
|
||||
* @param {Boolean} [load_fields] whether or not to load the fields as well
|
||||
* @return {Deferred}
|
||||
*/
|
||||
load_views: function (load_fields) {
|
||||
var self = this;
|
||||
var views = [];
|
||||
_.each(this.views, function (view) {
|
||||
if (!view.fields_view) {
|
||||
views.push([view.view_id, view.type]);
|
||||
}
|
||||
});
|
||||
var options = {
|
||||
action_id: this.action.id,
|
||||
load_fields: load_fields,
|
||||
toolbar: this.flags.sidebar,
|
||||
};
|
||||
if (this.flags.search_view && !this.search_fields_view) {
|
||||
options.load_filters = true;
|
||||
var searchview_id = this.action.search_view_id && this.action.search_view_id[0];
|
||||
views.push([searchview_id || false, 'search']);
|
||||
}
|
||||
return data_manager.load_views(this.dataset, views, options).then(function (fields_views) {
|
||||
_.each(fields_views, function (fields_view, view_type) {
|
||||
if (view_type === 'search') {
|
||||
self.search_fields_view = fields_view;
|
||||
} else {
|
||||
self.views[view_type].fields_view = fields_view;
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Returns the default view with the following fallbacks:
|
||||
@@ -131,51 +161,60 @@ var ViewManager = Widget.extend(ControlPanelMixin, {
|
||||
this.currently_switching = true; // prevent overlapping switches
|
||||
}
|
||||
|
||||
if (view.multi_record) {
|
||||
this.view_stack = [];
|
||||
} else if (this.view_stack.length > 0 && !(_.last(this.view_stack).multi_record)) {
|
||||
// Replace the last view by the new one if both are mono_record
|
||||
this.view_stack.pop();
|
||||
}
|
||||
this.view_stack.push(view);
|
||||
this.active_view = view;
|
||||
|
||||
if (!view.loaded) {
|
||||
if (!view.controller) {
|
||||
view.controller = this.create_view(view, view_options);
|
||||
}
|
||||
view.$fragment = $('<div>');
|
||||
view.loaded = view.controller.appendTo(view.$fragment).done(function () {
|
||||
// Remove the unnecessary outer div
|
||||
view.$fragment = view.$fragment.contents();
|
||||
self.trigger("controller_inited", view.type, view.controller);
|
||||
});
|
||||
// Ensure that the fields_view has been loaded
|
||||
var views_def;
|
||||
if (!view.fields_view) {
|
||||
views_def = this.load_views(view.require_fields);
|
||||
}
|
||||
|
||||
// Call do_search on the searchview to compute domains, contexts and groupbys
|
||||
if (this.search_view_loaded &&
|
||||
this.flags.auto_search &&
|
||||
view.controller.searchable !== false) {
|
||||
this.active_search = $.Deferred();
|
||||
$.when(this.search_view_loaded, view.loaded).done(function() {
|
||||
self.searchview.do_search();
|
||||
});
|
||||
}
|
||||
|
||||
return $.when(view.loaded, this.active_search)
|
||||
.then(function() {
|
||||
return self._display_view(view_options, old_view).then(function() {
|
||||
self.trigger('switch_mode', view_type, view_options);
|
||||
});
|
||||
}).fail(function(e) {
|
||||
if (!(e && e.code === 200 && e.data.exception_type)) {
|
||||
self.do_warn(_t("Error"), view.controller.display_name + _t(" view couldn't be loaded"));
|
||||
}
|
||||
// Restore internal state
|
||||
self.active_view = old_view;
|
||||
return $.when(views_def).then(function () {
|
||||
if (view.multi_record) {
|
||||
self.view_stack = [];
|
||||
} else if (self.view_stack.length > 0 && !(_.last(self.view_stack).multi_record)) {
|
||||
// Replace the last view by the new one if both are mono_record
|
||||
self.view_stack.pop();
|
||||
}).always(function () {
|
||||
self.currently_switching = false;
|
||||
}
|
||||
self.view_stack.push(view);
|
||||
|
||||
self.active_view = view;
|
||||
|
||||
if (!view.loaded) {
|
||||
if (!view.controller) {
|
||||
view.controller = self.create_view(view, view_options);
|
||||
}
|
||||
view.$fragment = $('<div>');
|
||||
view.loaded = view.controller.appendTo(view.$fragment).done(function () {
|
||||
// Remove the unnecessary outer div
|
||||
view.$fragment = view.$fragment.contents();
|
||||
self.trigger("controller_inited", view.type, view.controller);
|
||||
});
|
||||
}
|
||||
|
||||
// Call do_search on the searchview to compute domains, contexts and groupbys
|
||||
if (self.search_view_loaded &&
|
||||
self.flags.auto_search &&
|
||||
view.controller.searchable !== false) {
|
||||
self.active_search = $.Deferred();
|
||||
$.when(self.search_view_loaded, view.loaded).done(function() {
|
||||
self.searchview.do_search();
|
||||
});
|
||||
}
|
||||
|
||||
return $.when(view.loaded, self.active_search)
|
||||
.then(function() {
|
||||
return self._display_view(view_options, old_view).then(function() {
|
||||
self.trigger('switch_mode', view_type, view_options);
|
||||
});
|
||||
}).fail(function(e) {
|
||||
if (!(e && e.code === 200 && e.data.exception_type)) {
|
||||
self.do_warn(_t("Error"), view.controller.display_name + _t(" view couldn't be loaded"));
|
||||
}
|
||||
// Restore internal state
|
||||
self.active_view = old_view;
|
||||
self.view_stack.pop();
|
||||
});
|
||||
}).always(function () {
|
||||
self.currently_switching = false;
|
||||
});
|
||||
},
|
||||
_display_view: function (view_options, old_view) {
|
||||
@@ -218,17 +257,12 @@ var ViewManager = Widget.extend(ControlPanelMixin, {
|
||||
var self = this;
|
||||
var View = this.registry.get(view.type);
|
||||
var options = _.clone(view.options);
|
||||
|
||||
if (view.type === "form" && ((this.action && (this.action.target === 'new' || this.action.target === 'inline')) ||
|
||||
if (view.type === "form" && ((this.action.target === 'new' || this.action.target === 'inline') ||
|
||||
(view_options && view_options.mode === 'edit'))) {
|
||||
options.initial_mode = options.initial_mode || 'edit';
|
||||
}
|
||||
|
||||
var controller = new View(this, this.dataset, view.view_id, options);
|
||||
|
||||
if (view.embedded_view) {
|
||||
controller.set_embedded_view(view.embedded_view);
|
||||
}
|
||||
var controller = new View(this, this.dataset, view.fields_view, options);
|
||||
|
||||
controller.on('switch_mode', this, this.switch_mode.bind(this));
|
||||
controller.on('history_back', this, function () {
|
||||
@@ -306,12 +340,6 @@ var ViewManager = Widget.extend(ControlPanelMixin, {
|
||||
}
|
||||
return this.active_view.control_elements;
|
||||
},
|
||||
/**
|
||||
* @returns {Number|Boolean} the view id of the given type, false if not found
|
||||
*/
|
||||
get_view_id: function(view_type) {
|
||||
return this.views[view_type] && this.views[view_type].view_id || false;
|
||||
},
|
||||
/**
|
||||
* Sets up the current viewmanager's search view.
|
||||
* Sets $searchview and $searchview_buttons in control_elements to send to the ControlPanel
|
||||
@@ -325,28 +353,24 @@ var ViewManager = Widget.extend(ControlPanelMixin, {
|
||||
this.searchview.destroy();
|
||||
}
|
||||
|
||||
var view_id = (this.action && this.action.search_view_id && this.action.search_view_id[0]) || false;
|
||||
|
||||
var search_defaults = {};
|
||||
|
||||
var context = this.action ? this.action.context : [];
|
||||
var context = this.action.context || [];
|
||||
_.each(context, function (value, key) {
|
||||
var match = /^search_default_(.*)$/.exec(key);
|
||||
if (match) {
|
||||
search_defaults[match[1]] = value;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
var options = {
|
||||
hidden: this.flags.search_view === false,
|
||||
disable_custom_filters: this.flags.search_disable_custom_filters,
|
||||
$buttons: $("<div>"),
|
||||
action: this.action,
|
||||
search_defaults: search_defaults,
|
||||
};
|
||||
// Instantiate the SearchView, but do not append it nor its buttons to the DOM as this will
|
||||
// be done later, simultaneously to all other ControlPanel elements
|
||||
this.searchview = new SearchView(this, this.dataset, view_id, search_defaults, options);
|
||||
this.searchview = new SearchView(this, this.dataset, this.search_fields_view, options);
|
||||
|
||||
this.searchview.on('search_data', this, this.search.bind(this));
|
||||
return $.when(this.searchview.appendTo($("<div>"))).done(function() {
|
||||
|
||||
@@ -3,6 +3,7 @@ odoo.define('web.form_common', function (require) {
|
||||
|
||||
var core = require('web.core');
|
||||
var data = require('web.data');
|
||||
var data_manager = require('web.data_manager');
|
||||
var Dialog = require('web.Dialog');
|
||||
var ListView = require('web.ListView');
|
||||
var pyeval = require('web.pyeval');
|
||||
@@ -769,7 +770,7 @@ var AbstractField = FormWidget.extend(FieldInterface, {
|
||||
on_translate: function() {
|
||||
var self = this;
|
||||
var trans = new data.DataSet(this, 'ir.translation');
|
||||
return trans.call_button('translate_fields', [this.view.dataset.model, this.view.datarecord.id, this.name, this.view.dataset.get_context()]).done(function(r) {
|
||||
return trans.call_button('translate_fields', [this.view.model, this.view.datarecord.id, this.name, this.view.dataset.get_context()]).done(function(r) {
|
||||
self.do_action(r);
|
||||
});
|
||||
},
|
||||
@@ -793,7 +794,6 @@ var ViewDialog = Dialog.extend({ // FIXME should use ViewManager
|
||||
* options:
|
||||
* -readonly: only applicable when not in creation mode, default to false
|
||||
* - alternative_form_view
|
||||
* - view_id
|
||||
* - write_function
|
||||
* - read_function
|
||||
* - create_function
|
||||
@@ -814,7 +814,6 @@ var ViewDialog = Dialog.extend({ // FIXME should use ViewManager
|
||||
this.context = options.context || {};
|
||||
this.options = _.extend(this.options || {}, options || {});
|
||||
|
||||
this.on('closed', this, this.select);
|
||||
this.on_selected = options.on_selected || (function() {});
|
||||
},
|
||||
|
||||
@@ -838,6 +837,8 @@ var ViewDialog = Dialog.extend({ // FIXME should use ViewManager
|
||||
};
|
||||
this.dataset.parent_view = this.options.parent_view;
|
||||
this.dataset.child_name = this.options.child_name;
|
||||
|
||||
this.on('closed', this, this.select);
|
||||
},
|
||||
|
||||
select: function() {
|
||||
@@ -898,33 +899,36 @@ var FormViewDialog = ViewDialog.extend({
|
||||
|
||||
open: function() {
|
||||
var self = this;
|
||||
self._super();
|
||||
self.init_dataset();
|
||||
var _super = this._super.bind(this);
|
||||
this.init_dataset();
|
||||
|
||||
if (self.res_id) {
|
||||
self.dataset.ids = [self.res_id];
|
||||
self.dataset.index = 0;
|
||||
if (this.res_id) {
|
||||
this.dataset.ids = [this.res_id];
|
||||
this.dataset.index = 0;
|
||||
} else {
|
||||
self.dataset.index = null;
|
||||
this.dataset.index = null;
|
||||
}
|
||||
var options = _.clone(self.options.form_view_options) || {};
|
||||
if (self.res_id !== null) {
|
||||
options.initial_mode = self.options.readonly ? "view" : "edit";
|
||||
var options = _.clone(this.options.form_view_options) || {};
|
||||
if (this.res_id !== null) {
|
||||
options.initial_mode = this.options.readonly ? "view" : "edit";
|
||||
}
|
||||
_.extend(options, {
|
||||
$buttons: self.$buttons,
|
||||
$buttons: this.$buttons,
|
||||
});
|
||||
var FormView = core.view_registry.get('form');
|
||||
self.view_form = new FormView(self, self.dataset, self.options.view_id || false, options);
|
||||
if (self.options.alternative_form_view) {
|
||||
self.view_form.set_embedded_view(self.options.alternative_form_view);
|
||||
var fields_view_def;
|
||||
if (this.options.alternative_form_view) {
|
||||
fields_view_def = $.when(this.options.alternative_form_view);
|
||||
} else {
|
||||
fields_view_def = data_manager.load_fields_view(this.dataset, this.options.view_id, 'form', false);
|
||||
}
|
||||
|
||||
self.do_hide();
|
||||
self.view_form.appendTo(self.$el);
|
||||
self.view_form.on("form_view_loaded", self, function() {
|
||||
self.view_form.do_show().then(function() {
|
||||
self.do_show();
|
||||
fields_view_def.then(function (fields_view) {
|
||||
self.view_form = new FormView(self, self.dataset, fields_view, options);
|
||||
var fragment = document.createDocumentFragment();
|
||||
self.view_form.appendTo(fragment).then(function () {
|
||||
self.view_form.do_show().then(function() {
|
||||
_super().$el.append(fragment);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -965,49 +969,60 @@ var SelectCreateDialog = ViewDialog.extend({
|
||||
},
|
||||
|
||||
open: function() {
|
||||
this._super();
|
||||
this.init_dataset();
|
||||
if (this.options.initial_view == "search") {
|
||||
var context = pyeval.sync_eval_domains_and_contexts({
|
||||
domains: [],
|
||||
contexts: [this.context]
|
||||
}).context;
|
||||
var search_defaults = {};
|
||||
_.each(context, function (value_, key) {
|
||||
var match = /^search_default_(.*)$/.exec(key);
|
||||
if (match) {
|
||||
search_defaults[match[1]] = value_;
|
||||
}
|
||||
});
|
||||
this.setup(search_defaults);
|
||||
} else { // "form"
|
||||
this.create_edit_record();
|
||||
if(this.options.initial_view !== "search") {
|
||||
return this.create_edit_record();
|
||||
}
|
||||
|
||||
var _super = this._super.bind(this);
|
||||
this.init_dataset();
|
||||
var context = pyeval.sync_eval_domains_and_contexts({
|
||||
domains: [],
|
||||
contexts: [this.context]
|
||||
}).context;
|
||||
var search_defaults = {};
|
||||
_.each(context, function (value_, key) {
|
||||
var match = /^search_default_(.*)$/.exec(key);
|
||||
if (match) {
|
||||
search_defaults[match[1]] = value_;
|
||||
}
|
||||
});
|
||||
data_manager
|
||||
.load_views(this.dataset, [[false, 'list'], [false, 'search']], {})
|
||||
.then(this.setup.bind(this, search_defaults))
|
||||
.then(function (fragment) {
|
||||
_super().$el.append(fragment);
|
||||
});
|
||||
return this;
|
||||
},
|
||||
|
||||
setup: function(search_defaults) {
|
||||
setup: function(search_defaults, fields_views) {
|
||||
var self = this;
|
||||
if (this.searchview) {
|
||||
this.searchview.destroy();
|
||||
}
|
||||
var $header = $('<div/>').addClass('o_modal_header').appendTo(this.$el);
|
||||
var fragment = document.createDocumentFragment();
|
||||
var $header = $('<div/>').addClass('o_modal_header').appendTo(fragment);
|
||||
var $pager = $('<div/>').addClass('o_pager').appendTo($header);
|
||||
var $buttons = $('<div/>').addClass('o_search_options').appendTo($header);
|
||||
this.searchview = new SearchView(this, this.dataset, false, search_defaults, {$buttons: $buttons});
|
||||
this.searchview.on('search_data', self, function(domains, contexts, groupbys) {
|
||||
if (self.initial_ids) {
|
||||
self.do_search(domains.concat([[["id", "in", self.initial_ids]], self.domain]),
|
||||
contexts.concat(self.context), groupbys);
|
||||
self.initial_ids = undefined;
|
||||
var options = {
|
||||
$buttons: $('<div/>').addClass('o_search_options').appendTo($header),
|
||||
search_defaults: search_defaults,
|
||||
};
|
||||
|
||||
this.searchview = new SearchView(this, this.dataset, fields_views.search, options);
|
||||
this.searchview.on('search_data', this, function(domains, contexts, groupbys) {
|
||||
if (this.initial_ids) {
|
||||
this.do_search(domains.concat([[["id", "in", this.initial_ids]], this.domain]),
|
||||
contexts.concat(this.context), groupbys);
|
||||
this.initial_ids = undefined;
|
||||
} else {
|
||||
self.do_search(domains.concat([self.domain]), contexts.concat(self.context), groupbys);
|
||||
this.do_search(domains.concat([this.domain]), contexts.concat(this.context), groupbys);
|
||||
}
|
||||
});
|
||||
this.searchview.prependTo($header).done(function() {
|
||||
return this.searchview.prependTo($header).then(function() {
|
||||
self.searchview.toggle_visibility(true);
|
||||
|
||||
self.view_list = new SelectCreateListView(self,
|
||||
self.dataset, false,
|
||||
self.dataset, fields_views.list,
|
||||
_.extend({'deletable': false,
|
||||
'selectable': !self.options.disable_multiple_selection,
|
||||
'import_enabled': false,
|
||||
@@ -1019,17 +1034,6 @@ var SelectCreateDialog = ViewDialog.extend({
|
||||
e.cancel = true;
|
||||
});
|
||||
self.view_list.popup = self;
|
||||
self.view_list.appendTo(self.$el).then(function() {
|
||||
self.view_list.do_show();
|
||||
self.view_list.render_pager($pager);
|
||||
}).then(function() {
|
||||
if (self.options.initial_facet) {
|
||||
self.searchview.query.reset([self.options.initial_facet], {
|
||||
preventSearch: true,
|
||||
});
|
||||
}
|
||||
self.searchview.do_search();
|
||||
});
|
||||
|
||||
var buttons = [
|
||||
{text: _t("Cancel"), classes: "btn-default o_form_button_cancel", close: true}
|
||||
@@ -1045,6 +1049,19 @@ var SelectCreateDialog = ViewDialog.extend({
|
||||
}});
|
||||
}
|
||||
self.set_buttons(buttons);
|
||||
|
||||
return self.view_list.appendTo(fragment).then(function() {
|
||||
self.view_list.do_show();
|
||||
self.view_list.render_pager($pager);
|
||||
if (self.options.initial_facet) {
|
||||
self.searchview.query.reset([self.options.initial_facet], {
|
||||
preventSearch: true,
|
||||
});
|
||||
}
|
||||
self.searchview.do_search();
|
||||
|
||||
return fragment;
|
||||
});
|
||||
});
|
||||
},
|
||||
do_search: function(domains, contexts, groupbys) {
|
||||
@@ -1060,8 +1077,8 @@ var SelectCreateDialog = ViewDialog.extend({
|
||||
this.$footer.find(".o_selectcreatepopup_search_select").prop('disabled', this.selected_ids.length <= 0);
|
||||
},
|
||||
create_edit_record: function() {
|
||||
new FormViewDialog(this.getParent(), this.options).open();
|
||||
this.close();
|
||||
return new FormViewDialog(this.__parentedParent, this.options).open();
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@ var Model = require('web.DataModel');
|
||||
var session = require('web.session');
|
||||
var utils = require('web.utils');
|
||||
var ViewManager = require('web.ViewManager');
|
||||
var formats = require('web.formats');
|
||||
|
||||
var _t = core._t;
|
||||
var QWeb = core.qweb;
|
||||
@@ -752,11 +751,9 @@ var FieldX2Many = AbstractManyField.extend({
|
||||
var view = {
|
||||
view_id: false,
|
||||
view_type: view_type === "tree" ? "list" : view_type,
|
||||
options: {}
|
||||
fields_view: self.field.views && self.field.views[view_type],
|
||||
options: {},
|
||||
};
|
||||
if (self.field.views && self.field.views[view_type]) {
|
||||
view.embedded_view = self.field.views[view_type];
|
||||
}
|
||||
if(view.view_type === "list") {
|
||||
_.extend(view.options, {
|
||||
action_buttons: false, // to avoid 'Save' and 'Discard' buttons to appear in X2M fields
|
||||
@@ -774,6 +771,7 @@ var FieldX2Many = AbstractManyField.extend({
|
||||
}
|
||||
} else if (view.view_type === "kanban") {
|
||||
_.extend(view.options, {
|
||||
action_buttons: true,
|
||||
confirm_on_delete: false,
|
||||
});
|
||||
if (self.get("effective_readonly")) {
|
||||
@@ -824,9 +822,7 @@ var FieldX2Many = AbstractManyField.extend({
|
||||
});
|
||||
});
|
||||
$.async_when().done(function () {
|
||||
if (!self.isDestroyed()) {
|
||||
self.viewmanager.appendTo(self.$el);
|
||||
}
|
||||
self.alive( self.viewmanager.appendTo(self.$el));
|
||||
});
|
||||
return def;
|
||||
},
|
||||
@@ -899,7 +895,7 @@ var X2ManyDataSet = data.BufferedDataSet.extend({
|
||||
var X2ManyViewManager = ViewManager.extend({
|
||||
init: function(parent, dataset, views, flags, x2many_views) {
|
||||
// By default, render buttons and pager in X2M fields, but no sidebar
|
||||
var flags = _.extend({}, flags, {
|
||||
flags = _.extend({}, flags, {
|
||||
headless: false,
|
||||
search_view: false,
|
||||
action_buttons: true,
|
||||
@@ -925,7 +921,6 @@ var X2ManyViewManager = ViewManager.extend({
|
||||
res_model: self.x2m.field.relation,
|
||||
res_id: id,
|
||||
context: self.x2m.build_context(),
|
||||
|
||||
title: _t("Open: ") + self.x2m.string,
|
||||
create_function: function(data, options) {
|
||||
return self.x2m.data_create(data, options);
|
||||
@@ -952,7 +947,6 @@ var X2ManyViewManager = ViewManager.extend({
|
||||
|
||||
var X2ManyListView = ListView.extend({
|
||||
is_valid: function () {
|
||||
var self = this;
|
||||
if (!this.fields_view || !this.editable()){
|
||||
return true;
|
||||
}
|
||||
@@ -966,7 +960,7 @@ var X2ManyListView = ListView.extend({
|
||||
field.no_rerender = true;
|
||||
current_values[field.name] = field.get('value');
|
||||
});
|
||||
var cached_records = _.filter(this.dataset.cache, function(item){return !_.isEmpty(item.values)});
|
||||
var cached_records = _.filter(this.dataset.cache, function(item){return !_.isEmpty(item.values);});
|
||||
var valid = _.every(cached_records, function(record){
|
||||
_.each(fields, function(field){
|
||||
var value = record.values[field.name];
|
||||
@@ -1018,14 +1012,16 @@ var X2ManyList = ListView.List.extend({
|
||||
.click(function (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
// FIXME: there should also be an API for that one
|
||||
if (self.view.editor.form.__blur_timeout) {
|
||||
clearTimeout(self.view.editor.form.__blur_timeout);
|
||||
self.view.editor.form.__blur_timeout = false;
|
||||
var def;
|
||||
if (self.view.editable()) {
|
||||
// FIXME: there should also be an API for that one
|
||||
if (self.view.editor.form.__blur_timeout) {
|
||||
clearTimeout(self.view.editor.form.__blur_timeout);
|
||||
self.view.editor.form.__blur_timeout = false;
|
||||
}
|
||||
def = self.view.save_edition();
|
||||
}
|
||||
self.view.save_edition().done(function () {
|
||||
self.view.do_add_record();
|
||||
});
|
||||
$.when(def).done(self.view.do_add_record.bind(self));
|
||||
}));
|
||||
|
||||
var $padding = this.$current.find('tr:not([data-id]):first');
|
||||
@@ -1040,12 +1036,12 @@ var X2ManyList = ListView.List.extend({
|
||||
|
||||
var One2ManyListView = X2ManyListView.extend({
|
||||
_template: 'One2Many.listview',
|
||||
init: function (parent, dataset, view_id, options) {
|
||||
var self = this;
|
||||
this._super(parent, dataset, view_id, _.extend(options || {}, {
|
||||
init: function () {
|
||||
this._super.apply(this, arguments);
|
||||
this.options = _.extend(this.options, {
|
||||
GroupsType: One2ManyGroups,
|
||||
ListType: X2ManyList
|
||||
}));
|
||||
});
|
||||
this.on('edit:after', this, this.proxy('_after_edit'));
|
||||
this.on('save:before cancel:before', this, this.proxy('_before_unedit'));
|
||||
|
||||
@@ -1292,15 +1288,12 @@ var FieldOne2Many = FieldX2Many.extend({
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* @class
|
||||
* @extends instance.web.ListView
|
||||
*/
|
||||
var Many2ManyListView = X2ManyListView.extend(/** @lends instance.web.form.Many2ManyListView# */{
|
||||
init: function (parent, dataset, view_id, options) {
|
||||
this._super(parent, dataset, view_id, _.extend(options || {}, {
|
||||
var Many2ManyListView = X2ManyListView.extend({
|
||||
init: function () {
|
||||
this._super.apply(this, arguments);
|
||||
this.options = _.extend(this.options, {
|
||||
ListType: X2ManyList,
|
||||
}));
|
||||
});
|
||||
},
|
||||
do_add_record: function () {
|
||||
var self = this;
|
||||
@@ -1322,7 +1315,7 @@ var Many2ManyListView = X2ManyListView.extend(/** @lends instance.web.form.Many2
|
||||
do_activate_record: function(index, id) {
|
||||
var self = this;
|
||||
var pop = new common.FormViewDialog(this, {
|
||||
res_model: this.dataset.model,
|
||||
res_model: this.model,
|
||||
res_id: id,
|
||||
context: this.x2m.build_context(),
|
||||
title: _t("Open: ") + this.x2m.string,
|
||||
|
||||
@@ -16,45 +16,29 @@ var _lt = core._lt;
|
||||
var QWeb = core.qweb;
|
||||
var Class = core.Class;
|
||||
|
||||
/** @namespace */
|
||||
// instance.web.form = instance.web.form || {};
|
||||
|
||||
/**
|
||||
* Properties:
|
||||
* - actual_mode: always "view", "edit" or "create". Read-only property. Determines
|
||||
* the mode used by the view.
|
||||
*/
|
||||
var FormView = View.extend(common.FieldManagerMixin, {
|
||||
/**
|
||||
* Indicates that this view is not searchable, and thus that no search
|
||||
* view should be displayed (if there is one active).
|
||||
*/
|
||||
accesskey: "F",
|
||||
defaults: _.extend({}, View.prototype.defaults, {
|
||||
not_interactible_on_create: false,
|
||||
initial_mode: "view",
|
||||
disable_autofocus: false,
|
||||
footer_to_buttons: false,
|
||||
}),
|
||||
display_name: _lt('Form'),
|
||||
icon: 'fa-edit',
|
||||
multi_record: false,
|
||||
// Indicates that this view is not searchable, and thus that no search view should be displayed.
|
||||
searchable: false,
|
||||
template: "FormView",
|
||||
display_name: _lt('Form'),
|
||||
view_type: "form",
|
||||
multi_record: false,
|
||||
accesskey: "F",
|
||||
icon: 'fa-edit',
|
||||
/**
|
||||
* @constructs instance.web.FormView
|
||||
* @extends instance.web.View
|
||||
*
|
||||
* @param {instance.web.Session} session the current openerp session
|
||||
* @param {instance.web.DataSet} dataset the dataset this view will work with
|
||||
* @param {String} view_id the identifier of the OpenERP view object
|
||||
*
|
||||
* @property {instance.web.Registry} registry=instance.web.form.widgets widgets registry for this form view instance
|
||||
*/
|
||||
init: function(parent, dataset, view_id, options) {
|
||||
|
||||
init: function() {
|
||||
var self = this;
|
||||
this._super(parent);
|
||||
this.ViewManager = parent;
|
||||
this.set_default_options(options);
|
||||
this.dataset = dataset;
|
||||
this.model = dataset.model;
|
||||
this.view_id = view_id || false;
|
||||
this.fields_view = {};
|
||||
this._super.apply(this, arguments);
|
||||
this.fields = {};
|
||||
this.fields_order = [];
|
||||
this.datarecord = {};
|
||||
@@ -67,12 +51,6 @@ var FormView = View.extend(common.FieldManagerMixin, {
|
||||
this.widgets_registry = core.form_custom_registry;
|
||||
this.has_been_loaded = $.Deferred();
|
||||
this.translatable_fields = [];
|
||||
_.defaults(this.options, {
|
||||
"not_interactible_on_create": false,
|
||||
"initial_mode": "view",
|
||||
"disable_autofocus": false,
|
||||
"footer_to_buttons": false,
|
||||
});
|
||||
this.is_initialized = $.Deferred();
|
||||
this.mutating_mutex = new utils.Mutex();
|
||||
this.save_list = [];
|
||||
@@ -104,34 +82,13 @@ var FormView = View.extend(common.FieldManagerMixin, {
|
||||
});
|
||||
});
|
||||
},
|
||||
view_loading: function(r) {
|
||||
return this.load_form(r);
|
||||
},
|
||||
destroy: function() {
|
||||
_.each(this.get_widgets(), function(w) {
|
||||
w.off('focused blurred');
|
||||
w.destroy();
|
||||
});
|
||||
if (this.$el) {
|
||||
this.$el.off('.formBlur');
|
||||
}
|
||||
this._super();
|
||||
},
|
||||
load_form: function(data) {
|
||||
start: function() {
|
||||
var self = this;
|
||||
if (!data) {
|
||||
throw new Error(_t("No data provided."));
|
||||
}
|
||||
if (this.arch) {
|
||||
throw "Form view does not support multiple calls to load_form";
|
||||
}
|
||||
this.fields_order = [];
|
||||
this.fields_view = data;
|
||||
|
||||
this.rendering_engine.set_fields_registry(this.fields_registry);
|
||||
this.rendering_engine.set_tags_registry(this.tags_registry);
|
||||
this.rendering_engine.set_widgets_registry(this.widgets_registry);
|
||||
this.rendering_engine.set_fields_view(data);
|
||||
this.rendering_engine.set_fields_view(this.fields_view);
|
||||
var $dest = this.$el.hasClass("oe_form_container") ? this.$el : this.$el.find('.oe_form_container');
|
||||
this.rendering_engine.render_to($dest);
|
||||
|
||||
@@ -143,7 +100,7 @@ var FormView = View.extend(common.FieldManagerMixin, {
|
||||
|
||||
// Add bounce effect on button 'Edit' when click on readonly page view.
|
||||
this.$el.find(".oe_form_group_row,.oe_form_field,label,h1,.oe_title,.oe_notebook_page, .oe_list_content").on('click', function (e) {
|
||||
if(self.get("actual_mode") == "view" && self.$buttons && !$(e.target).is('[data-toggle]')) {
|
||||
if(self.get("actual_mode") === "view" && self.$buttons && !$(e.target).is('[data-toggle]')) {
|
||||
var $button = self.$buttons.find(".oe_form_button_edit");
|
||||
$button.openerpBounce();
|
||||
e.stopPropagation();
|
||||
@@ -152,14 +109,23 @@ var FormView = View.extend(common.FieldManagerMixin, {
|
||||
});
|
||||
//bounce effect on red button when click on statusbar.
|
||||
this.$el.find(".oe_form_field_status:not(.oe_form_status_clickable)").on('click', function (e) {
|
||||
if((self.get("actual_mode") == "view")) {
|
||||
if((self.get("actual_mode") === "view")) {
|
||||
var $button = self.$el.find(".oe_highlight:not(.o_form_invisible)").css({'float':'left','clear':'none'});
|
||||
$button.openerpBounce();
|
||||
e.stopPropagation();
|
||||
}
|
||||
});
|
||||
this.trigger('form_view_loaded', data);
|
||||
return $.when();
|
||||
return this._super();
|
||||
},
|
||||
destroy: function() {
|
||||
_.each(this.get_widgets(), function(w) {
|
||||
w.off('focused blurred');
|
||||
w.destroy();
|
||||
});
|
||||
if (this.$el) {
|
||||
this.$el.off('.formBlur');
|
||||
}
|
||||
this._super();
|
||||
},
|
||||
/**
|
||||
* Render the buttons according to the FormView.buttons template and add listeners on it.
|
||||
@@ -181,14 +147,10 @@ var FormView = View.extend(common.FieldManagerMixin, {
|
||||
|
||||
// Show or hide the buttons according to the view mode
|
||||
this.toggle_buttons();
|
||||
this.$buttons.on('click', '.oe_form_button_create',
|
||||
this.guard_active(this.on_button_create));
|
||||
this.$buttons.on('click', '.oe_form_button_edit',
|
||||
this.guard_active(this.on_button_edit));
|
||||
this.$buttons.on('click', '.oe_form_button_save',
|
||||
this.guard_active(this.on_button_save));
|
||||
this.$buttons.on('click', '.oe_form_button_cancel',
|
||||
this.guard_active(this.on_button_cancel));
|
||||
this.$buttons.on('click', '.oe_form_button_create', this.on_button_create);
|
||||
this.$buttons.on('click', '.oe_form_button_edit', this.on_button_edit);
|
||||
this.$buttons.on('click', '.oe_form_button_save', this.on_button_save);
|
||||
this.$buttons.on('click', '.oe_form_button_cancel', this.on_button_cancel);
|
||||
|
||||
if ($node) {
|
||||
this.$buttons.appendTo($node);
|
||||
@@ -311,10 +273,8 @@ var FormView = View.extend(common.FieldManagerMixin, {
|
||||
}
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {Object} [options]
|
||||
* @param {Boolean} [mode=undefined] If specified, switch the form to specified mode. Can be "edit" or "view".
|
||||
* @param {Boolean} [reload=true] whether the form should reload its content on show, or use the currently loaded record
|
||||
* @param {Boolean} [options.mode=undefined] If specified, switch the form to specified mode. Can be "edit" or "view".
|
||||
* @param {Boolean} [options.reload=true] whether the form should reload its content on show, or use the currently loaded record
|
||||
* @return {$.Deferred}
|
||||
*/
|
||||
do_show: function (options) {
|
||||
@@ -521,7 +481,7 @@ var FormView = View.extend(common.FieldManagerMixin, {
|
||||
// In case of a o2m virtual id, we should pass an empty ids list
|
||||
ids.push(self.datarecord.id);
|
||||
}
|
||||
def = self.alive(new Model(self.dataset.model).call(
|
||||
def = self.alive(self.dataset.call(
|
||||
"onchange", [ids, values, trigger_field_name, onchange_specs, context]));
|
||||
}
|
||||
this.onchanges_mutex.exec(function(){
|
||||
@@ -1199,7 +1159,7 @@ var FormView = View.extend(common.FieldManagerMixin, {
|
||||
all_users = d.$el.find('#formview_default_all').is(':checked');
|
||||
new data.DataSet(self, 'ir.values').call(
|
||||
'set_default', [
|
||||
self.dataset.model,
|
||||
self.model,
|
||||
field_to_set,
|
||||
self.fields[field_to_set].get_value(),
|
||||
all_users,
|
||||
|
||||
@@ -5,8 +5,8 @@ odoo.define('web.GraphView', function (require) {
|
||||
*---------------------------------------------------------*/
|
||||
|
||||
var core = require('web.core');
|
||||
var data_manager = require('web.data_manager');
|
||||
var GraphWidget = require('web.GraphWidget');
|
||||
var Model = require('web.DataModel');
|
||||
var View = require('web.View');
|
||||
|
||||
var _lt = core._lt;
|
||||
@@ -17,22 +17,31 @@ var GraphView = View.extend({
|
||||
className: 'oe_graph',
|
||||
display_name: _lt('Graph'),
|
||||
icon: 'fa-bar-chart',
|
||||
view_type: 'graph',
|
||||
require_fields: true,
|
||||
|
||||
init: function(parent, dataset, view_id, options) {
|
||||
this._super(parent, dataset, view_id, options);
|
||||
init: function () {
|
||||
this._super.apply(this, arguments);
|
||||
|
||||
this.model = new Model(dataset.model, {group_by_no_leaf: true});
|
||||
this.measures = [];
|
||||
this.active_measure = '__count__';
|
||||
this.initial_groupbys = [];
|
||||
this.widget = undefined;
|
||||
},
|
||||
start: function () {
|
||||
var load_fields = this.model.call('fields_get', [], {context: this.dataset.get_context()})
|
||||
.then(this.prepare_fields.bind(this));
|
||||
|
||||
return $.when(this._super(), load_fields);
|
||||
willStart: function () {
|
||||
var self = this;
|
||||
var fields_def = data_manager.load_fields(this.dataset).then(this.prepare_fields.bind(this));
|
||||
this.fields_view.arch.children.forEach(function (field) {
|
||||
var name = field.attrs.name;
|
||||
if (field.attrs.interval) {
|
||||
name += ':' + field.attrs.interval;
|
||||
}
|
||||
if (field.attrs.type === 'measure') {
|
||||
self.active_measure = name;
|
||||
} else {
|
||||
self.initial_groupbys.push(name);
|
||||
}
|
||||
});
|
||||
return $.when(this._super(), fields_def);
|
||||
},
|
||||
/**
|
||||
* Render the buttons according to the GraphView.buttons and
|
||||
@@ -59,20 +68,6 @@ var GraphView = View.extend({
|
||||
$(li).toggleClass('selected', $(li).data('field') === self.active_measure);
|
||||
});
|
||||
},
|
||||
view_loading: function (fvg) {
|
||||
var self = this;
|
||||
fvg.arch.children.forEach(function (field) {
|
||||
var name = field.attrs.name;
|
||||
if (field.attrs.interval) {
|
||||
name += ':' + field.attrs.interval;
|
||||
}
|
||||
if (field.attrs.type === 'measure') {
|
||||
self.active_measure = name;
|
||||
} else {
|
||||
self.initial_groupbys.push(name);
|
||||
}
|
||||
});
|
||||
},
|
||||
do_show: function () {
|
||||
this.do_push_state({});
|
||||
return this._super();
|
||||
@@ -92,7 +87,7 @@ var GraphView = View.extend({
|
||||
do_search: function (domain, context, group_by) {
|
||||
if (!this.widget) {
|
||||
this.initial_groupbys = context.graph_groupbys || this.initial_groupbys;
|
||||
this.widget = new GraphWidget(this, this.dataset.model, {
|
||||
this.widget = new GraphWidget(this, this.model, {
|
||||
measure: context.graph_measure || this.active_measure,
|
||||
mode: context.graph_mode || this.active_mode,
|
||||
domain: domain,
|
||||
|
||||
@@ -32,33 +32,33 @@ var row_decoration = [
|
||||
'decoration-warning'
|
||||
];
|
||||
|
||||
var ListView = View.extend( /** @lends instance.web.ListView# */ {
|
||||
var ListView = View.extend({
|
||||
_template: 'ListView',
|
||||
accesskey: 'L',
|
||||
icon: 'fa-list-ul',
|
||||
display_name: _lt('List'),
|
||||
defaults: {
|
||||
defaults: _.extend({}, View.prototype.defaults, {
|
||||
// records can be selected one by one
|
||||
'selectable': true,
|
||||
selectable: true,
|
||||
// list rows can be deleted
|
||||
'deletable': false,
|
||||
deletable: false,
|
||||
// whether the column headers should be displayed
|
||||
'header': true,
|
||||
header: true,
|
||||
// display addition button, with that label
|
||||
'addable': _lt("Create"),
|
||||
addable: _lt("Create"),
|
||||
// whether the list view can be sorted, note that once a view has been
|
||||
// sorted it can not be reordered anymore
|
||||
'sortable': true,
|
||||
sortable: true,
|
||||
// whether the view rows can be reordered (via vertical drag & drop)
|
||||
'reorderable': true,
|
||||
'action_buttons': true,
|
||||
reorderable: true,
|
||||
action_buttons: true,
|
||||
//whether the editable property of the view has to be disabled
|
||||
'disable_editable_mode': false,
|
||||
},
|
||||
view_type: 'tree',
|
||||
disable_editable_mode: false,
|
||||
}),
|
||||
events: {
|
||||
'click thead th.oe_sortable[data-id]': 'sort_by_column'
|
||||
},
|
||||
icon: 'fa-list-ul',
|
||||
|
||||
/**
|
||||
* Core class for list-type displays.
|
||||
*
|
||||
@@ -69,11 +69,11 @@ var ListView = View.extend( /** @lends instance.web.ListView# */ {
|
||||
* See constructor parameters and method documentations for information on
|
||||
* the default behaviors and possible options for the list view.
|
||||
*
|
||||
* @constructs instance.web.ListView
|
||||
* @extends instance.web.View
|
||||
* @constructs ListView
|
||||
* @extends View
|
||||
*
|
||||
* @param parent parent object
|
||||
* @param {instance.web.DataSet} dataset the dataset the view should work with
|
||||
* @param {DataSet} dataset the dataset the view should work with
|
||||
* @param {String} view_id the listview's identifier, if any
|
||||
* @param {Object} options A set of options used to configure the view
|
||||
* @param {Boolean} [options.selectable=true] determines whether view rows are selectable (e.g. via a checkbox)
|
||||
@@ -83,13 +83,14 @@ var ListView = View.extend( /** @lends instance.web.ListView# */ {
|
||||
* @param {Boolean} [options.sortable=true] is it possible to sort the table by clicking on column headers
|
||||
* @param {Boolean} [options.reorderable=true] is it possible to reorder list rows
|
||||
*/
|
||||
init: function(parent, dataset, view_id, options) {
|
||||
init: function() {
|
||||
var self = this;
|
||||
this._super(parent);
|
||||
this.set_default_options(_.extend({}, this.defaults, options || {}));
|
||||
this.dataset = dataset;
|
||||
this.model = dataset.model;
|
||||
this.view_id = view_id;
|
||||
this._super.apply(this, arguments);
|
||||
this.options = _.defaults(this.options, {
|
||||
GroupsType: ListView.Groups,
|
||||
ListType: ListView.List,
|
||||
});
|
||||
|
||||
this.previous_colspan = null;
|
||||
this.colors = null;
|
||||
this.fonts = null;
|
||||
@@ -105,8 +106,8 @@ var ListView = View.extend( /** @lends instance.web.ListView# */ {
|
||||
} else {
|
||||
this.groups.datagroup = new DataGroup(
|
||||
this, this.model,
|
||||
dataset.get_domain(),
|
||||
dataset.get_context());
|
||||
this.dataset.get_domain(),
|
||||
this.dataset.get_context());
|
||||
this.groups.datagroup.sort = this.dataset._sort;
|
||||
}
|
||||
|
||||
@@ -125,21 +126,22 @@ var ListView = View.extend( /** @lends instance.web.ListView# */ {
|
||||
if (!this.options.$pager || !this.options.$pager.length) {
|
||||
this.options.$pager = false;
|
||||
}
|
||||
|
||||
this.name = "" + this.fields_view.arch.attrs.string;
|
||||
|
||||
// the view's number of records per page (|| section)
|
||||
this._limit = (this.options.limit ||
|
||||
this.defaults.limit ||
|
||||
(this.getParent().action || {}).limit ||
|
||||
parseInt(this.fields_view.arch.attrs.limit, 10) ||
|
||||
80);
|
||||
},
|
||||
view_loading: function(r) {
|
||||
return this.load_list(r);
|
||||
},
|
||||
set_default_options: function (options) {
|
||||
this._super(options);
|
||||
_.defaults(this.options, {
|
||||
GroupsType: ListView.Groups,
|
||||
ListType: ListView.List
|
||||
});
|
||||
|
||||
//Sort
|
||||
var default_order = this.fields_view.arch.attrs.default_order,
|
||||
unsorted = !this.dataset._sort.length;
|
||||
if (unsorted && default_order && !this.grouped) {
|
||||
this.dataset.set_sort(default_order.split(','));
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Set a custom Group construct as the root of the List View.
|
||||
@@ -169,14 +171,41 @@ var ListView = View.extend( /** @lends instance.web.ListView# */ {
|
||||
}
|
||||
});
|
||||
},
|
||||
/**
|
||||
* View startup method, the default behavior is to set the ``oe_list``
|
||||
* class on its root element and to perform an RPC load call.
|
||||
*
|
||||
* @returns {$.Deferred} loading promise
|
||||
*/
|
||||
willStart: function() {
|
||||
// Retrieve the decoration defined on the model's list view
|
||||
this.decoration = _.pick(this.fields_view.arch.attrs, function(value, key) {
|
||||
return row_decoration.indexOf(key) >= 0;
|
||||
});
|
||||
this.decoration = _.mapObject(this.decoration, function(value) {
|
||||
return py.parse(py.tokenize(value));
|
||||
});
|
||||
|
||||
this.setup_columns(this.fields_view.fields, this.grouped);
|
||||
|
||||
return this._super();
|
||||
},
|
||||
start: function() {
|
||||
var self = this;
|
||||
this.$el.html(QWeb.render(this._template, this));
|
||||
this.$el.addClass(this.fields_view.arch.attrs['class']);
|
||||
this.$el.addClass('oe_list o_list_view');
|
||||
|
||||
// Selecting records
|
||||
this.$('.oe_list_record_selector').click(function() {
|
||||
var checked = self.$('.oe_list_record_selector').prop('checked');
|
||||
self.$('.oe_list_record_selector input').prop('checked', checked || false);
|
||||
var selection = self.groups.get_selection();
|
||||
$(self.groups).trigger('selected', [selection.ids, selection.records]);
|
||||
});
|
||||
|
||||
if(this.dataset._sort.length){
|
||||
if(this.dataset._sort[0].indexOf('-') === -1){
|
||||
this.$el.find('th[data-id=' + this.dataset._sort[0] + ']').addClass("sortdown");
|
||||
}else {
|
||||
this.$el.find('th[data-id=' + this.dataset._sort[0].split('-')[1] + ']').addClass("sortup");
|
||||
}
|
||||
}
|
||||
this.trigger('list_view_loaded', data, this.grouped);
|
||||
return this._super();
|
||||
},
|
||||
/**
|
||||
@@ -201,73 +230,6 @@ var ListView = View.extend( /** @lends instance.web.ListView# */ {
|
||||
});
|
||||
return classnames;
|
||||
},
|
||||
/**
|
||||
* Called after loading the list view's description, sets up such things
|
||||
* as the view table's columns, renders the table itself and hooks up the
|
||||
* various table-level and row-level DOM events (action buttons, deletion
|
||||
* buttons, selection of records, [New] button, selection of a given
|
||||
* record, ...)
|
||||
*
|
||||
* Sets up the following:
|
||||
*
|
||||
* * Processes arch and fields to generate a complete field descriptor for each field
|
||||
* * Create the table itself and allocate visible columns
|
||||
* * Hook in the top-level (header) [New|Add] and [Delete] button
|
||||
* * Sets up showing/hiding the top-level [Delete] button based on records being selected or not
|
||||
* * Sets up event handlers for action buttons and per-row deletion button
|
||||
* * Hooks global callback for clicking on a row
|
||||
*
|
||||
* @param {Object} data wrapped fields_view_get result
|
||||
* @param {Object} data.fields_view fields_view_get result (processed)
|
||||
* @param {Object} data.fields_view.fields mapping of fields for the current model
|
||||
* @param {Object} data.fields_view.arch current list view descriptor
|
||||
* @param {Boolean} grouped Is the list view grouped
|
||||
*/
|
||||
load_list: function(data) {
|
||||
var self = this;
|
||||
this.fields_view = data;
|
||||
this.name = "" + this.fields_view.arch.attrs.string;
|
||||
this._limit = parseInt(this.fields_view.arch.attrs.limit, 10) || this._limit;
|
||||
|
||||
// Retrieve the decoration defined on the model's list view
|
||||
this.decoration = _.pick(this.fields_view.arch.attrs, function(value, key) {
|
||||
return row_decoration.indexOf(key) >= 0;
|
||||
});
|
||||
this.decoration = _.mapObject(this.decoration, function(value) {
|
||||
return py.parse(py.tokenize(value));
|
||||
});
|
||||
|
||||
this.setup_columns(this.fields_view.fields, this.grouped);
|
||||
|
||||
this.$el.html(QWeb.render(this._template, this));
|
||||
this.$el.addClass(this.fields_view.arch.attrs['class']);
|
||||
|
||||
// Head hook
|
||||
// Selecting records
|
||||
this.$el.find('.oe_list_record_selector').click(function(){
|
||||
self.$el.find('.oe_list_record_selector input').prop('checked',
|
||||
self.$el.find('.oe_list_record_selector').prop('checked') || false);
|
||||
var selection = self.groups.get_selection();
|
||||
$(self.groups).trigger(
|
||||
'selected', [selection.ids, selection.records]);
|
||||
});
|
||||
|
||||
//Sort
|
||||
var default_order = this.fields_view.arch.attrs.default_order,
|
||||
unsorted = !this.dataset._sort.length;
|
||||
if (unsorted && default_order && !this.grouped) {
|
||||
this.dataset.set_sort(default_order.split(','));
|
||||
}
|
||||
|
||||
if(this.dataset._sort.length){
|
||||
if(this.dataset._sort[0].indexOf('-') == -1){
|
||||
this.$el.find('th[data-id=' + this.dataset._sort[0] + ']').addClass("sortdown");
|
||||
}else {
|
||||
this.$el.find('th[data-id=' + this.dataset._sort[0].split('-')[1] + ']').addClass("sortup");
|
||||
}
|
||||
}
|
||||
this.trigger('list_view_loaded', data, this.grouped);
|
||||
},
|
||||
/**
|
||||
* Render the buttons according to the ListView.buttons template and
|
||||
* add listeners on it.
|
||||
@@ -499,16 +461,6 @@ var ListView = View.extend( /** @lends instance.web.ListView# */ {
|
||||
this.sidebar.do_hide();
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Reloads the list view based on the current settings (dataset & al)
|
||||
*
|
||||
* @deprecated
|
||||
* @param {Boolean} [grouped] Should the list be displayed grouped
|
||||
* @param {Object} [context] context to send the server while loading the view
|
||||
*/
|
||||
reload_view: function (grouped, context) {
|
||||
return this.load_view(context);
|
||||
},
|
||||
/**
|
||||
* re-renders the content of the list view
|
||||
*
|
||||
@@ -617,8 +569,7 @@ var ListView = View.extend( /** @lends instance.web.ListView# */ {
|
||||
this.no_leaf = !!context['group_by_no_leaf'];
|
||||
this.grouped = !!group_by;
|
||||
|
||||
return this.alive(this.load_view(context)).then(
|
||||
this.proxy('reload_content'));
|
||||
return this.reload_content();
|
||||
},
|
||||
/**
|
||||
* Handles the signal to delete lines from the records list
|
||||
@@ -720,7 +671,7 @@ var ListView = View.extend( /** @lends instance.web.ListView# */ {
|
||||
c.set_eval_context(_.extend({
|
||||
active_id: id,
|
||||
active_ids: [id],
|
||||
active_model: this.dataset.model
|
||||
active_model: this.model
|
||||
}, this.records.get(id).toContext()));
|
||||
if (action.context) {
|
||||
c.add(action.context);
|
||||
@@ -960,7 +911,7 @@ var ListView = View.extend( /** @lends instance.web.ListView# */ {
|
||||
});
|
||||
core.view_registry.add('list', ListView);
|
||||
|
||||
ListView.List = Class.extend( /** @lends instance.web.ListView.List# */{
|
||||
ListView.List = Class.extend({
|
||||
/**
|
||||
* List display for the ListView, handles basic DOM events and transforms
|
||||
* them in the relevant higher-level events, to which the list view (or
|
||||
@@ -1187,7 +1138,7 @@ ListView.List = Class.extend( /** @lends instance.web.ListView.List# */{
|
||||
}
|
||||
}
|
||||
return column.format(record.toForm().data, {
|
||||
model: this.dataset.model,
|
||||
model: this.model,
|
||||
id: record.get('id')
|
||||
});
|
||||
},
|
||||
@@ -1299,7 +1250,7 @@ ListView.List = Class.extend( /** @lends instance.web.ListView.List# */{
|
||||
}
|
||||
});
|
||||
|
||||
ListView.Groups = Class.extend( /** @lends instance.web.ListView.Groups# */{
|
||||
ListView.Groups = Class.extend({
|
||||
passthrough_events: 'action deleted row_link',
|
||||
/**
|
||||
* Grouped display for the ListView. Handles basic DOM events and interacts
|
||||
|
||||
@@ -38,19 +38,18 @@ var Editor = Widget.extend({
|
||||
delegate: this.getParent(),
|
||||
});
|
||||
this.delegate = this.options.delegate;
|
||||
|
||||
this.record = null;
|
||||
this.form = new (this.options.formView)(this, this.delegate.dataset, false, {
|
||||
},
|
||||
start: function () {
|
||||
var self = this;
|
||||
var form_fields_view = this._validate_view(this.delegate.edition_view(this));
|
||||
this.form = new (this.options.formView)(this, this.delegate.dataset, form_fields_view, {
|
||||
initial_mode: 'edit',
|
||||
is_list_editable: true,
|
||||
disable_autofocus: true,
|
||||
$buttons: $(),
|
||||
$pager: $(),
|
||||
});
|
||||
},
|
||||
start: function () {
|
||||
var self = this;
|
||||
this.form.embedded_view = this._validate_view(this.delegate.edition_view(this));
|
||||
return $.when(this._super(), this.form.appendTo($('<div/>')).then(function() {
|
||||
self.form.$el.addClass(self.$el.attr('class'));
|
||||
self.replaceElement(self.form.$el);
|
||||
@@ -258,7 +257,7 @@ ListView.include(/** @lends instance.web.ListView# */{
|
||||
this._super.apply(this, arguments);
|
||||
}
|
||||
},
|
||||
load_list: function (data, grouped) {
|
||||
start: function () {
|
||||
// tree/@editable takes priority on everything else if present.
|
||||
var result = this._super.apply(this, arguments);
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ odoo.define('web.PivotView', function (require) {
|
||||
|
||||
var core = require('web.core');
|
||||
var crash_manager = require('web.crash_manager');
|
||||
var data_manager = require('web.data_manager');
|
||||
var formats = require('web.formats');
|
||||
var framework = require('web.framework');
|
||||
var Model = require('web.DataModel');
|
||||
@@ -19,10 +20,6 @@ var _t = core._t;
|
||||
var QWeb = core.qweb;
|
||||
|
||||
var PivotView = View.extend({
|
||||
template: 'PivotView',
|
||||
icon: 'fa-table',
|
||||
display_name: _lt('Pivot'),
|
||||
view_type: 'pivot',
|
||||
events: {
|
||||
'click .oe-opened': 'on_open_header_click',
|
||||
'click .oe-closed': 'on_closed_header_click',
|
||||
@@ -30,17 +27,20 @@ var PivotView = View.extend({
|
||||
'click td': 'on_cell_click',
|
||||
'click .measure-row': 'on_measure_row_click',
|
||||
},
|
||||
display_name: _lt('Pivot'),
|
||||
icon: 'fa-table',
|
||||
require_fields: true,
|
||||
template: 'PivotView',
|
||||
|
||||
init: function(parent, dataset, view_id, options) {
|
||||
this._super(parent, dataset, view_id, options);
|
||||
this.model = new Model(dataset.model, {group_by_no_leaf: true});
|
||||
init: function() {
|
||||
this._super.apply(this, arguments);
|
||||
this._model = new Model(this.model, {group_by_no_leaf: true});
|
||||
|
||||
this.fields = {};
|
||||
this.measures = {};
|
||||
this.groupable_fields = {};
|
||||
this.ready = false; // will be ready after the first do_search
|
||||
this.data_loaded = $.Deferred();
|
||||
this.title = options.title;
|
||||
this.title = this.options.title || this.fields_view.arch.attrs.string;
|
||||
|
||||
this.main_row = {
|
||||
root: undefined,
|
||||
@@ -63,30 +63,72 @@ var PivotView = View.extend({
|
||||
|
||||
this.numbering = {};
|
||||
this.widgets = [];
|
||||
|
||||
this.enable_linking = !this.fields_view.arch.attrs.disable_linking;
|
||||
},
|
||||
willStart: function () {
|
||||
var self = this;
|
||||
return session.rpc('/web/pivot/check_xlwt').then(function(result) {
|
||||
|
||||
var fields_def = data_manager.load_fields(this.dataset);
|
||||
var xlwt_def = session.rpc('/web/pivot/check_xlwt').then(function(result) {
|
||||
self.xlwt_installed = result;
|
||||
});
|
||||
|
||||
this.fields_view.arch.children.forEach(function (field) {
|
||||
var name = field.attrs.name;
|
||||
if (field.attrs.interval) {
|
||||
name += ':' + field.attrs.interval;
|
||||
}
|
||||
//noinspection FallThroughInSwitchStatementJS
|
||||
switch (field.attrs.type) {
|
||||
case 'measure':
|
||||
self.widgets.push(field.attrs.widget || "");
|
||||
self.active_measures.push(name);
|
||||
break;
|
||||
case 'col':
|
||||
self.initial_col_groupby.push(name);
|
||||
break;
|
||||
default:
|
||||
if ('operator' in field.attrs) {
|
||||
self.active_measures.push(name);
|
||||
break;
|
||||
}
|
||||
case 'row':
|
||||
self.initial_row_groupby.push(name);
|
||||
}
|
||||
});
|
||||
if ((!this.active_measures.length) || this.fields_view.arch.attrs.display_quantity) {
|
||||
this.active_measures.push('__count__');
|
||||
}
|
||||
return $.when(fields_def, xlwt_def, this._super()).then(function (fields) {
|
||||
self.prepare_fields(fields);
|
||||
// add active measures to the measure list. This is very rarely necessary, but it
|
||||
// can be useful if one is working with a functional field non stored, but in a
|
||||
// model with an overrided read_group method. In this case, the pivot view could
|
||||
// work, and the measure should be allowed. However, be careful if you define a
|
||||
// measure in your pivot view: non stored functional fields will probably not work
|
||||
// (their aggregate will always be 0).
|
||||
_.each(self.active_measures, function (m) {
|
||||
if (!(m in self.measures)) {
|
||||
self.measures[m] = self.fields[m];
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
start: function () {
|
||||
this.$table_container = this.$('.o-pivot-table');
|
||||
|
||||
var load_fields = this.model.call('fields_get', [], {context: this.dataset.get_context()})
|
||||
.then(this.prepare_fields.bind(this));
|
||||
|
||||
return $.when(this._super(), load_fields).then(this.render_field_selection.bind(this));
|
||||
},
|
||||
render_field_selection: function () {
|
||||
var self = this;
|
||||
|
||||
var context = {fields: _.chain(this.groupable_fields).pairs().sortBy(function(f){return f[1].string;}).value()};
|
||||
this.$el.toggleClass('oe_enable_linking', this.enable_linking);
|
||||
var context = {
|
||||
fields: _.chain(this.groupable_fields).pairs().sortBy(function(f) {
|
||||
return f[1].string;
|
||||
}).value(),
|
||||
};
|
||||
this.$field_selection = this.$('.o-field-selection');
|
||||
this.$field_selection.html(QWeb.render('PivotView.FieldSelection', context));
|
||||
core.bus.on('click', self, function () {
|
||||
self.$field_selection.find('ul').first().hide();
|
||||
core.bus.on('click', this, function () {
|
||||
this.$field_selection.find('ul').first().hide();
|
||||
});
|
||||
return this._super();
|
||||
},
|
||||
/**
|
||||
* Render the buttons according to the PivotView.buttons template and
|
||||
@@ -127,49 +169,6 @@ var PivotView = View.extend({
|
||||
this.sidebar.appendTo($node);
|
||||
}
|
||||
},
|
||||
view_loading: function (fvg) {
|
||||
var self = this;
|
||||
this.title = this.title || fvg.arch.attrs.string;
|
||||
this.enable_linking = !fvg.arch.attrs.disable_linking;
|
||||
this.$el.toggleClass('oe-enable-linking', this.enable_linking);
|
||||
fvg.arch.children.forEach(function (field) {
|
||||
var name = field.attrs.name;
|
||||
if (field.attrs.interval) {
|
||||
name += ':' + field.attrs.interval;
|
||||
}
|
||||
//noinspection FallThroughInSwitchStatementJS
|
||||
switch (field.attrs.type) {
|
||||
case 'measure':
|
||||
self.widgets.push(field.attrs.widget || "");
|
||||
self.active_measures.push(name);
|
||||
break;
|
||||
case 'col':
|
||||
self.initial_col_groupby.push(name);
|
||||
break;
|
||||
default:
|
||||
if ('operator' in field.attrs) {
|
||||
self.active_measures.push(name);
|
||||
break;
|
||||
}
|
||||
case 'row':
|
||||
self.initial_row_groupby.push(name);
|
||||
}
|
||||
});
|
||||
if ((!self.active_measures.length) || fvg.arch.attrs.display_quantity){
|
||||
self.active_measures.push('__count__');
|
||||
}
|
||||
// add active measures to the measure list. This is very rarely necessary, but it
|
||||
// can be useful if one is working with a functional field non stored, but in a
|
||||
// model with an overrided read_group method. In this case, the pivot view could
|
||||
// work, and the measure should be allowed. However, be careful if you define a
|
||||
// measure in your pivot view: non stored functional fields will probably not work
|
||||
// (their aggregate will always be 0).
|
||||
_.each(this.active_measures, function (m) {
|
||||
if (!(m in self.measures)) {
|
||||
self.measures[m] = self.fields[m];
|
||||
}
|
||||
});
|
||||
},
|
||||
prepare_fields: function (fields) {
|
||||
var self = this,
|
||||
groupable_types = ['many2one', 'char', 'boolean',
|
||||
@@ -277,15 +276,14 @@ var PivotView = View.extend({
|
||||
col_domain = this.headers[col_id].domain,
|
||||
context = _.omit(_.clone(this.context), 'group_by');
|
||||
|
||||
var views = [
|
||||
[this.options.action_views_ids.list || false, 'list'],
|
||||
[this.options.action_views_ids.form || false, 'form']
|
||||
];
|
||||
var views = _.filter(this.options.action.views, function (view) {
|
||||
return view[1] === 'form' || view[1] === 'list';
|
||||
});
|
||||
|
||||
return this.do_action({
|
||||
type: 'ir.actions.act_window',
|
||||
name: this.title,
|
||||
res_model: this.model.name,
|
||||
res_model: this.model,
|
||||
views: views,
|
||||
view_type : "list",
|
||||
view_mode : "list",
|
||||
@@ -345,7 +343,7 @@ var PivotView = View.extend({
|
||||
groupbys.push([field].concat(other_groupbys.slice(0,i)));
|
||||
}
|
||||
return $.when.apply(null, groupbys.map(function (groupby) {
|
||||
return self.model.query(fields)
|
||||
return self._model.query(fields)
|
||||
.filter(header.domain.length ? header.domain : self.domain)
|
||||
.context(self.context)
|
||||
.lazy(false)
|
||||
@@ -398,7 +396,7 @@ var PivotView = View.extend({
|
||||
}
|
||||
}
|
||||
return $.when.apply(null, groupbys.map(function (groupby) {
|
||||
return self.model.query(fields)
|
||||
return self._model.query(fields)
|
||||
.filter(self.domain)
|
||||
.context(self.context)
|
||||
.lazy(false)
|
||||
|
||||
@@ -68,7 +68,9 @@ var Input = Widget.extend( /** @lends instance.web.search.Input# */{
|
||||
"get_domain not implemented for widget " + this.attrs.type);
|
||||
},
|
||||
load_attrs: function (attrs) {
|
||||
attrs.modifiers = attrs.modifiers ? JSON.parse(attrs.modifiers) : {};
|
||||
if (!_.isObject(attrs.modifiers)) {
|
||||
attrs.modifiers = attrs.modifiers ? JSON.parse(attrs.modifiers) : {};
|
||||
}
|
||||
this.attrs = attrs;
|
||||
},
|
||||
/**
|
||||
|
||||
@@ -2,7 +2,7 @@ odoo.define('web.FavoriteMenu', function (require) {
|
||||
"use strict";
|
||||
|
||||
var core = require('web.core');
|
||||
var Model = require('web.DataModel');
|
||||
var data_manager = require('web.data_manager');
|
||||
var pyeval = require('web.pyeval');
|
||||
var session = require('web.session');
|
||||
var Widget = require('web.Widget');
|
||||
@@ -26,18 +26,18 @@ return Widget.extend({
|
||||
}
|
||||
},
|
||||
},
|
||||
init: function (parent, query, target_model, action_id) {
|
||||
init: function (parent, query, target_model, action_id, filters) {
|
||||
this._super.apply(this,arguments);
|
||||
this.searchview = parent;
|
||||
this.query = query;
|
||||
this.target_model = target_model;
|
||||
this.model = new Model('ir.filters');
|
||||
this.filters = {};
|
||||
this.$filters = {};
|
||||
this.action_id = action_id;
|
||||
this.filters = {};
|
||||
_.each(filters, this.add_filter.bind(this));
|
||||
},
|
||||
start: function () {
|
||||
var self = this;
|
||||
this.$filters = {};
|
||||
this.$save_search = this.$('.oe-save-search');
|
||||
this.$save_name = this.$('.oe-save-name');
|
||||
this.$inputs = this.$save_name.find('input');
|
||||
@@ -55,12 +55,10 @@ return Widget.extend({
|
||||
}
|
||||
})
|
||||
.on('reset', this.proxy('clear_selection'));
|
||||
return this.model.call('get_filters', [this.target_model, this.action_id],
|
||||
{context: this.searchview.dataset.context})
|
||||
.done(this.proxy('prepare_dropdown_menu'));
|
||||
},
|
||||
prepare_dropdown_menu: function (filters) {
|
||||
filters.map(this.append_filter.bind(this));
|
||||
|
||||
_.each(this.filters, this.append_filter.bind(this));
|
||||
|
||||
return this._super();
|
||||
},
|
||||
toggle_save_menu: function (is_open) {
|
||||
this.$save_search
|
||||
@@ -115,17 +113,18 @@ return Widget.extend({
|
||||
var filter = {
|
||||
name: filter_name,
|
||||
user_id: shared_filter ? false : session.uid,
|
||||
model_id: this.searchview.dataset.model,
|
||||
model_id: this.target_model,
|
||||
context: results.context,
|
||||
domain: results.domain,
|
||||
sort: JSON.stringify(this.searchview.dataset._sort),
|
||||
is_default: default_filter,
|
||||
action_id: this.action_id,
|
||||
};
|
||||
return this.model.call('create_or_replace', [filter]).done(function (id) {
|
||||
return data_manager.create_filter(filter).done(function (id) {
|
||||
filter.id = id;
|
||||
self.toggle_save_menu(false);
|
||||
self.$save_name.find('input').val('').prop('checked', false);
|
||||
self.add_filter(filter);
|
||||
self.append_filter(filter);
|
||||
self.toggle_filter(filter, true);
|
||||
});
|
||||
@@ -187,31 +186,39 @@ return Widget.extend({
|
||||
clear_selection: function () {
|
||||
this.$('li.selected').removeClass('selected');
|
||||
},
|
||||
/**
|
||||
* Adds a filter description to the filters dict
|
||||
* @param {Object} [filter] the filter description
|
||||
*/
|
||||
add_filter: function (filter) {
|
||||
this.filters[this.key_for(filter)] = filter;
|
||||
},
|
||||
/**
|
||||
* Creates a $filter JQuery node, adds it to the $filters dict and appends it to the filter menu
|
||||
* @param {Object} [filter] the filter description
|
||||
*/
|
||||
append_filter: function (filter) {
|
||||
var self = this,
|
||||
key = this.key_for(filter),
|
||||
$filter;
|
||||
var self = this;
|
||||
var key = this.key_for(filter);
|
||||
|
||||
this.$divider.show();
|
||||
if (key in this.$filters) {
|
||||
$filter = this.$filters[key];
|
||||
} else {
|
||||
this.filters[key] = filter;
|
||||
$filter = $('<li></li>')
|
||||
.insertBefore(this.$divider)
|
||||
if (!(key in this.$filters)) {
|
||||
var $filter = $('<li>')
|
||||
.addClass(filter.user_id ? 'oe_searchview_custom_private'
|
||||
: 'oe_searchview_custom_public')
|
||||
.toggleClass('oe_searchview_custom_default', filter.is_default)
|
||||
.append($('<a>').text(filter.name));
|
||||
|
||||
.append($('<a>').text(filter.name))
|
||||
.append($('<span>', {
|
||||
class: 'fa fa-trash-o remove-filter',
|
||||
on: {
|
||||
click: function (event) {
|
||||
event.stopImmediatePropagation();
|
||||
self.remove_filter(filter, $filter, key);
|
||||
},
|
||||
},
|
||||
}))
|
||||
.insertBefore(this.$divider);
|
||||
this.$filters[key] = $filter;
|
||||
this.$filters[key].addClass(filter.user_id ? 'oe_searchview_custom_private'
|
||||
: 'oe_searchview_custom_public');
|
||||
$('<span>')
|
||||
.addClass('fa fa-trash-o remove-filter')
|
||||
.click(function (event) {
|
||||
event.stopImmediatePropagation();
|
||||
self.remove_filter(filter, $filter, key);
|
||||
})
|
||||
.appendTo($filter);
|
||||
}
|
||||
this.$filters[key].unbind('click').click(function () {
|
||||
self.toggle_filter(filter);
|
||||
@@ -244,14 +251,16 @@ return Widget.extend({
|
||||
if (!confirm(filter.user_id ? warning : global_warning)) {
|
||||
return;
|
||||
}
|
||||
this.model.call('unlink', [filter.id]).done(function () {
|
||||
$filter.remove();
|
||||
delete self.$filters[key];
|
||||
delete self.filters[key];
|
||||
if (_.isEmpty(self.filters)) {
|
||||
self.$divider.hide();
|
||||
}
|
||||
});
|
||||
return data_manager
|
||||
.delete_filter(filter)
|
||||
.done(function () {
|
||||
$filter.remove();
|
||||
delete self.$filters[key];
|
||||
delete self.filters[key];
|
||||
if (_.isEmpty(self.filters)) {
|
||||
self.$divider.hide();
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -260,6 +269,7 @@ return Widget.extend({
|
||||
odoo.define('web.FilterMenu', function (require) {
|
||||
"use strict";
|
||||
|
||||
var data_manager = require('web.data_manager');
|
||||
var search_filters = require('web.search_filters');
|
||||
var search_inputs = require('web.search_inputs');
|
||||
var Widget = require('web.Widget');
|
||||
@@ -282,22 +292,12 @@ return Widget.extend({
|
||||
}
|
||||
},
|
||||
},
|
||||
init: function (parent, filters, fields_def) {
|
||||
init: function (parent, filters) {
|
||||
this._super(parent);
|
||||
this.filters = filters || [];
|
||||
this.searchview = parent;
|
||||
this.propositions = [];
|
||||
this.fields_def = fields_def.then(function (data) {
|
||||
var fields = {
|
||||
id: { string: 'ID', type: 'id', searchable: true }
|
||||
};
|
||||
_.each(data, function(field_def, field_name) {
|
||||
if (field_def.selectable !== false && field_name !== 'id') {
|
||||
fields[field_name] = field_def;
|
||||
}
|
||||
});
|
||||
return fields;
|
||||
});
|
||||
this.custom_filters_open = false;
|
||||
},
|
||||
start: function () {
|
||||
var self = this;
|
||||
@@ -311,23 +311,41 @@ return Widget.extend({
|
||||
$('<li class="divider">').insertBefore(self.$add_filter);
|
||||
}
|
||||
});
|
||||
this.append_proposition().then(function (prop) {
|
||||
prop.$el.hide();
|
||||
});
|
||||
},
|
||||
get_fields: function () {
|
||||
if (!this._fields_def) {
|
||||
this._fields_def = data_manager.load_fields(this.searchview.dataset).then(function (data) {
|
||||
var fields = {
|
||||
id: { string: 'ID', type: 'id', searchable: true }
|
||||
};
|
||||
_.each(data, function(field_def, field_name) {
|
||||
if (field_def.selectable !== false && field_name !== 'id') {
|
||||
fields[field_name] = field_def;
|
||||
}
|
||||
});
|
||||
return fields;
|
||||
});
|
||||
}
|
||||
return this._fields_def;
|
||||
},
|
||||
toggle_custom_filter_menu: function (is_open) {
|
||||
this.$add_filter
|
||||
.toggleClass('o_closed_menu', !(_.isUndefined(is_open)) ? !is_open : undefined)
|
||||
.toggleClass('o_open_menu', is_open);
|
||||
this.$add_filter_menu.toggle(is_open);
|
||||
if (this.$add_filter.hasClass('o_closed_menu') && (!this.propositions.length)) {
|
||||
this.append_proposition();
|
||||
var self = this;
|
||||
this.custom_filters_open = !_.isUndefined(is_open) ? is_open : !this.custom_filters_open;
|
||||
var def;
|
||||
if (this.custom_filters_open && !this.propositions.length) {
|
||||
def = this.append_proposition();
|
||||
}
|
||||
this.$('.oe-filter-condition').toggle(is_open);
|
||||
$.when(def).then(function () {
|
||||
self.$add_filter
|
||||
.toggleClass('o_closed_menu', !self.custom_filters_open)
|
||||
.toggleClass('o_open_menu', self.custom_filters_open);
|
||||
self.$add_filter_menu.toggle(self.custom_filters_open);
|
||||
self.$('.oe-filter-condition').toggle(self.custom_filters_open);
|
||||
});
|
||||
},
|
||||
append_proposition: function () {
|
||||
var self = this;
|
||||
return this.fields_def.then(function (fields) {
|
||||
return this.get_fields().then(function (fields) {
|
||||
var prop = new search_filters.ExtendedSearchProposition(self, fields);
|
||||
self.propositions.push(prop);
|
||||
prop.insertBefore(self.$add_filter_menu);
|
||||
@@ -368,9 +386,10 @@ return Widget.extend({
|
||||
odoo.define('web.GroupByMenu', function (require) {
|
||||
"use strict";
|
||||
|
||||
var Widget = require('web.Widget');
|
||||
var core = require('web.core');
|
||||
var data_manager = require('web.data_manager');
|
||||
var search_inputs = require('web.search_inputs');
|
||||
var Widget = require('web.Widget');
|
||||
|
||||
var QWeb = core.qweb;
|
||||
|
||||
@@ -387,15 +406,13 @@ return Widget.extend({
|
||||
this.toggle_add_menu();
|
||||
},
|
||||
},
|
||||
init: function (parent, groups, fields_def) {
|
||||
init: function (parent, groups) {
|
||||
this._super(parent);
|
||||
this.groups = groups || [];
|
||||
this.groupable_fields = [];
|
||||
this.searchview = parent;
|
||||
this.fields_def = fields_def.then(this.proxy('get_groupable_fields'));
|
||||
},
|
||||
start: function () {
|
||||
var self = this;
|
||||
this.$menu = this.$('.group-by-menu');
|
||||
var divider = this.$menu.find('.divider');
|
||||
_.invoke(this.groups, 'insertBefore', divider);
|
||||
@@ -403,35 +420,43 @@ return Widget.extend({
|
||||
divider.show();
|
||||
}
|
||||
this.$add_group = this.$menu.find('.add-custom-group');
|
||||
this.fields_def.then(function () {
|
||||
self.$menu.append(QWeb.render('GroupByMenuSelector', self));
|
||||
self.$add_group_menu = self.$('.oe-add-group');
|
||||
self.$group_selector = self.$('.oe-group-selector');
|
||||
self.$('.oe-select-group').click(function () {
|
||||
self.toggle_add_menu(false);
|
||||
var field = self.$group_selector.find(':selected').data('name');
|
||||
self.add_groupby_to_menu(field);
|
||||
});
|
||||
});
|
||||
},
|
||||
get_groupable_fields: function (fields) {
|
||||
var groupable_types = ['many2one', 'char', 'boolean', 'selection', 'date', 'datetime'];
|
||||
var filter_group_field = _.filter(fields, function(field, name) {
|
||||
if (field.store && _.contains(groupable_types, field.type)) {
|
||||
field.name = name;
|
||||
return field;
|
||||
}
|
||||
});
|
||||
this.groupable_fields = _.sortBy(filter_group_field, 'string');
|
||||
get_fields: function () {
|
||||
var self = this;
|
||||
if (!this._fields_def) {
|
||||
this._fields_def = data_manager.load_fields(this.searchview.dataset).then(function (fields) {
|
||||
var groupable_types = ['many2one', 'char', 'boolean', 'selection', 'date', 'datetime'];
|
||||
var filter_group_field = _.filter(fields, function(field, name) {
|
||||
if (field.store && _.contains(groupable_types, field.type)) {
|
||||
field.name = name;
|
||||
return field;
|
||||
}
|
||||
});
|
||||
self.groupable_fields = _.sortBy(filter_group_field, 'string');
|
||||
|
||||
self.$menu.append(QWeb.render('GroupByMenuSelector', self));
|
||||
self.$add_group_menu = self.$('.oe-add-group');
|
||||
self.$group_selector = self.$('.oe-group-selector');
|
||||
self.$('.oe-select-group').click(function () {
|
||||
self.toggle_add_menu(false);
|
||||
var field = self.$group_selector.find(':selected').data('name');
|
||||
self.add_groupby_to_menu(field);
|
||||
});
|
||||
});
|
||||
}
|
||||
return this._fields_def;
|
||||
},
|
||||
toggle_add_menu: function (is_open) {
|
||||
this.$add_group
|
||||
.toggleClass('o_closed_menu', !(_.isUndefined(is_open)) ? !is_open : undefined)
|
||||
.toggleClass('o_open_menu', is_open);
|
||||
this.$add_group_menu.toggle(is_open);
|
||||
if (this.$add_group.hasClass('o_open_menu')) {
|
||||
this.$group_selector.focus();
|
||||
}
|
||||
var self = this;
|
||||
this.get_fields().then(function () {
|
||||
self.$add_group
|
||||
.toggleClass('o_closed_menu', !(_.isUndefined(is_open)) ? !is_open : undefined)
|
||||
.toggleClass('o_open_menu', is_open);
|
||||
self.$add_group_menu.toggle(is_open);
|
||||
if (self.$add_group.hasClass('o_open_menu')) {
|
||||
self.$group_selector.focus();
|
||||
}
|
||||
});
|
||||
},
|
||||
add_groupby_to_menu: function (field_name) {
|
||||
var filter = new search_inputs.Filter({attrs:{
|
||||
|
||||
@@ -3,13 +3,13 @@ odoo.define('web.SearchView', function (require) {
|
||||
|
||||
var AutoComplete = require('web.AutoComplete');
|
||||
var core = require('web.core');
|
||||
var data_manager = require('web.data_manager');
|
||||
var FavoriteMenu = require('web.FavoriteMenu');
|
||||
var FilterMenu = require('web.FilterMenu');
|
||||
var GroupByMenu = require('web.GroupByMenu');
|
||||
var Model = require('web.DataModel');
|
||||
var pyeval = require('web.pyeval');
|
||||
var search_inputs = require('web.search_inputs');
|
||||
var utils = require('web.utils');
|
||||
var View = require('web.View');
|
||||
var Widget = require('web.Widget');
|
||||
|
||||
var Backbone = window.Backbone;
|
||||
@@ -317,8 +317,14 @@ var FacetValueView = Widget.extend({
|
||||
}
|
||||
});
|
||||
|
||||
var SearchView = Widget.extend(/** @lends instance.web.SearchView# */{
|
||||
template: "SearchView",
|
||||
var SearchView = View.extend({
|
||||
defaults: _.extend({}, View.prototype.defaults, {
|
||||
hidden: false,
|
||||
disable_filters: false,
|
||||
disable_groupby: false,
|
||||
disable_favorites: false,
|
||||
disable_custom_filters: false,
|
||||
}),
|
||||
events: {
|
||||
// focus last input if view itself is clicked
|
||||
'click': function (e) {
|
||||
@@ -355,45 +361,47 @@ var SearchView = Widget.extend(/** @lends instance.web.SearchView# */{
|
||||
this.$el.autocomplete('widget').css('z-index', 9999);
|
||||
},
|
||||
},
|
||||
template: "SearchView",
|
||||
|
||||
/**
|
||||
* @constructs instance.web.SearchView
|
||||
* @extends instance.web.Widget
|
||||
* @constructs SearchView
|
||||
* @extends View
|
||||
*
|
||||
* @param parent
|
||||
* @param dataset
|
||||
* @param view_id
|
||||
* @param defaults
|
||||
* @param fields_view
|
||||
* @param {Object} [options]
|
||||
* @param {Boolean} [options.hidden=false] hide the search view
|
||||
* @param {Boolean} [options.disable_custom_filters=false] do not load custom filters from ir.filters
|
||||
*/
|
||||
init: function(parent, dataset, view_id, defaults, options) {
|
||||
this.options = _.defaults(options || {}, {
|
||||
hidden: false,
|
||||
disable_filters: false,
|
||||
disable_groupby: false,
|
||||
disable_favorites: false,
|
||||
disable_custom_filters: false,
|
||||
});
|
||||
this._super(parent);
|
||||
init: function() {
|
||||
this._super.apply(this, arguments);
|
||||
this.title = this.options.action && this.options.action.name;
|
||||
this.action_id = this.options.action && this.options.action.id;
|
||||
this.query = undefined;
|
||||
this.dataset = dataset;
|
||||
this.view_id = view_id;
|
||||
this.title = options.action && options.action.name;
|
||||
this.search_fields = [];
|
||||
this.filters = [];
|
||||
this.groupbys = [];
|
||||
this.visible_filters = (localStorage.visible_search_menu === 'true');
|
||||
this.input_subviews = []; // for user input in searchbar
|
||||
this.defaults = defaults || {};
|
||||
this.headless = this.options.hidden && _.isEmpty(this.defaults);
|
||||
this.search_defaults = this.options.search_defaults || {};
|
||||
this.headless = this.options.hidden && _.isEmpty(this.search_defaults);
|
||||
this.$buttons = this.options.$buttons;
|
||||
|
||||
this.filter_menu = undefined;
|
||||
this.groupby_menu = undefined;
|
||||
this.favorite_menu = undefined;
|
||||
this.action_id = this.options && this.options.action && this.options.action.id;
|
||||
},
|
||||
},
|
||||
willStart: function () {
|
||||
var self = this;
|
||||
var def;
|
||||
if (!this.options.disable_favorites) {
|
||||
def = data_manager.load_filters(this.dataset, this.action_id).then(function (filters) {
|
||||
self.favorite_filters = filters;
|
||||
});
|
||||
}
|
||||
return $.when(this._super(), def);
|
||||
},
|
||||
start: function() {
|
||||
if (this.headless) {
|
||||
this.do_hide();
|
||||
@@ -405,43 +413,29 @@ var SearchView = Widget.extend(/** @lends instance.web.SearchView# */{
|
||||
.on('add change reset remove', this.proxy('do_search'))
|
||||
.on('change', this.proxy('renderChangedFacets'))
|
||||
.on('add reset remove', this.proxy('renderFacets'));
|
||||
var load_view = this.dataset._model.fields_view_get({
|
||||
view_id: this.view_id,
|
||||
view_type: 'search',
|
||||
context: this.dataset.get_context(),
|
||||
});
|
||||
this.$('.oe_searchview_unfold_drawer')
|
||||
.toggleClass('fa-caret-down', !this.visible_filters)
|
||||
.toggleClass('fa-caret-up', this.visible_filters);
|
||||
return this.alive($.when(this._super(), this.alive(load_view).then(this.view_loaded.bind(this))));
|
||||
},
|
||||
get_title: function() {
|
||||
return this.title;
|
||||
},
|
||||
view_loaded: function (r) {
|
||||
var menu_defs = [];
|
||||
this.fields_view_get = r;
|
||||
this.view_id = this.view_id || r.view_id;
|
||||
this.prepare_search_inputs();
|
||||
if (this.$buttons) {
|
||||
var fields_def = new Model(this.dataset.model).call('fields_get', {
|
||||
context: this.dataset.get_context()
|
||||
});
|
||||
|
||||
if (!this.options.disable_filters) {
|
||||
this.filter_menu = new FilterMenu(this, this.filters, fields_def);
|
||||
this.filter_menu = new FilterMenu(this, this.filters);
|
||||
menu_defs.push(this.filter_menu.appendTo(this.$buttons));
|
||||
}
|
||||
if (!this.options.disable_groupby) {
|
||||
this.groupby_menu = new GroupByMenu(this, this.groupbys, fields_def);
|
||||
this.groupby_menu = new GroupByMenu(this, this.groupbys);
|
||||
menu_defs.push(this.groupby_menu.appendTo(this.$buttons));
|
||||
}
|
||||
if (!this.options.disable_favorites) {
|
||||
this.favorite_menu = new FavoriteMenu(this, this.query, this.dataset.model, this.action_id);
|
||||
this.favorite_menu = new FavoriteMenu(this, this.query, this.dataset.model, this.action_id, this.favorite_filters);
|
||||
menu_defs.push(this.favorite_menu.appendTo(this.$buttons));
|
||||
}
|
||||
}
|
||||
return $.when.apply($, menu_defs).then(this.proxy('set_default_filters'));
|
||||
return $.when.apply($, menu_defs).then(this.set_default_filters.bind(this));
|
||||
},
|
||||
get_title: function() {
|
||||
return this.title;
|
||||
},
|
||||
set_default_filters: function () {
|
||||
var self = this,
|
||||
@@ -449,10 +443,10 @@ var SearchView = Widget.extend(/** @lends instance.web.SearchView# */{
|
||||
if (!self.options.disable_custom_filters && default_custom_filter) {
|
||||
return this.favorite_menu.toggle_filter(default_custom_filter, true);
|
||||
}
|
||||
if (!_.isEmpty(this.defaults)) {
|
||||
if (!_.isEmpty(this.search_defaults)) {
|
||||
var inputs = this.search_fields.concat(this.filters, this.groupbys),
|
||||
defaults = _.invoke(inputs, 'facet_for_defaults', this.defaults);
|
||||
return $.when.apply(null, defaults).then(function () {
|
||||
search_defaults = _.invoke(inputs, 'facet_for_defaults', this.search_defaults);
|
||||
return $.when.apply(null, search_defaults).then(function () {
|
||||
self.query.reset(_(arguments).compact(), {preventSearch: true});
|
||||
});
|
||||
}
|
||||
@@ -667,7 +661,7 @@ var SearchView = Widget.extend(/** @lends instance.web.SearchView# */{
|
||||
// * this.group_by: group_bys
|
||||
prepare_search_inputs: function () {
|
||||
var self = this,
|
||||
arch = this.fields_view_get.arch;
|
||||
arch = this.fields_view.arch;
|
||||
|
||||
var filters = [].concat.apply([], _.map(arch.children, function (item) {
|
||||
return item.tag !== 'group' ? eval_item(item) : item.children.map(eval_item);
|
||||
@@ -702,7 +696,7 @@ var SearchView = Widget.extend(/** @lends instance.web.SearchView# */{
|
||||
}
|
||||
if (filter.item.tag === 'field') {
|
||||
var attrs = filter.item.attrs;
|
||||
var field = self.fields_view_get.fields[attrs.name];
|
||||
var field = self.fields_view.fields[attrs.name];
|
||||
|
||||
// M2O combined with selection widget is pointless and broken in search views,
|
||||
// but has been used in the past for unsupported hacks -> ignore it
|
||||
|
||||
@@ -14,41 +14,21 @@ var View = require('web.View');
|
||||
var _lt = core._lt;
|
||||
var QWeb = core.qweb;
|
||||
|
||||
var TreeView = View.extend(/** @lends instance.web.TreeView# */{
|
||||
var TreeView = View.extend({
|
||||
display_name: _lt('Tree'),
|
||||
icon: 'fa-align-left',
|
||||
view_type: 'tree',
|
||||
/**
|
||||
* Indicates that this view is not searchable, and thus that no search
|
||||
* view should be displayed (if there is one active).
|
||||
*/
|
||||
searchable : false,
|
||||
// Indicates that this view is not searchable, and thus that no search view should be displayed.
|
||||
searchable: false,
|
||||
|
||||
/**
|
||||
* Genuine tree view (the one displayed as a tree, not the list)
|
||||
*
|
||||
* @constructs instance.web.TreeView
|
||||
* @extends instance.web.View
|
||||
*
|
||||
* @param parent
|
||||
* @param dataset
|
||||
* @param view_id
|
||||
* @param options
|
||||
*/
|
||||
init: function(parent, dataset, view_id, options) {
|
||||
this._super(parent);
|
||||
this.dataset = dataset;
|
||||
this.model = dataset.model;
|
||||
this.view_id = view_id;
|
||||
|
||||
init: function() {
|
||||
this._super.apply(this, arguments);
|
||||
this.records = {};
|
||||
|
||||
this.options = _.extend({}, this.defaults, options || {});
|
||||
|
||||
_.bindAll(this, 'color_for');
|
||||
},
|
||||
|
||||
view_loading: function(r) {
|
||||
return this.load_tree(r);
|
||||
this.fields = this.fields_view.fields;
|
||||
this.children_field = this.fields_view.field_parent;
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -67,20 +47,29 @@ var TreeView = View.extend(/** @lends instance.web.TreeView# */{
|
||||
}
|
||||
return fields;
|
||||
},
|
||||
load_tree: function (fields_view) {
|
||||
var self = this;
|
||||
var has_toolbar = !!fields_view.arch.attrs.toolbar;
|
||||
// field name in OpenERP is kinda stupid: this is the name of the field
|
||||
// holding the ids to the children of the current node, why call it
|
||||
// field_parent?
|
||||
this.children_field = fields_view.field_parent;
|
||||
this.fields_view = fields_view;
|
||||
willStart: function () {
|
||||
_(this.fields_view.arch.children).each(function (field) {
|
||||
if (field.attrs.modifiers) {
|
||||
field.attrs.modifiers = JSON.parse(field.attrs.modifiers);
|
||||
}
|
||||
});
|
||||
this.fields = fields_view.fields;
|
||||
|
||||
if (this.fields_view.arch.attrs.colors) {
|
||||
this.colors = _(this.fields_view.arch.attrs.colors.split(';')).chain()
|
||||
.compact()
|
||||
.map(function(color_pair) {
|
||||
var pair = color_pair.split(':'),
|
||||
color = pair[0],
|
||||
expr = pair[1];
|
||||
return [color, py.parse(py.tokenize(expr)), expr];
|
||||
}).value();
|
||||
}
|
||||
|
||||
return this._super();
|
||||
},
|
||||
start: function () {
|
||||
var self = this;
|
||||
var has_toolbar = !!this.fields_view.arch.attrs.toolbar;
|
||||
this.hook_row_click();
|
||||
this.$el.html(QWeb.render('TreeView', {
|
||||
'title': this.fields_view.arch.attrs.string,
|
||||
@@ -120,17 +109,7 @@ var TreeView = View.extend(/** @lends instance.web.TreeView# */{
|
||||
// TODO store open nodes in url ?...
|
||||
this.do_push_state({});
|
||||
|
||||
if (!this.fields_view.arch.attrs.colors) {
|
||||
return;
|
||||
}
|
||||
this.colors = _(this.fields_view.arch.attrs.colors.split(';')).chain()
|
||||
.compact()
|
||||
.map(function(color_pair) {
|
||||
var pair = color_pair.split(':'),
|
||||
color = pair[0],
|
||||
expr = pair[1];
|
||||
return [color, py.parse(py.tokenize(expr)), expr];
|
||||
}).value();
|
||||
return this._super();
|
||||
},
|
||||
/**
|
||||
* Returns the color for the provided record in the current view (from the
|
||||
@@ -225,7 +204,7 @@ var TreeView = View.extend(/** @lends instance.web.TreeView# */{
|
||||
activate: function(id) {
|
||||
var self = this;
|
||||
var local_context = {
|
||||
active_model: self.dataset.model,
|
||||
active_model: self.model,
|
||||
active_id: id,
|
||||
active_ids: [id]
|
||||
};
|
||||
@@ -234,7 +213,7 @@ var TreeView = View.extend(/** @lends instance.web.TreeView# */{
|
||||
this.dataset.get_context(), local_context));
|
||||
return this.rpc('/web/treeview/action', {
|
||||
id: id,
|
||||
model: this.dataset.model,
|
||||
model: this.model,
|
||||
context: ctx
|
||||
}).then(function (actions) {
|
||||
if (!actions.length) { return; }
|
||||
@@ -260,11 +239,6 @@ var TreeView = View.extend(/** @lends instance.web.TreeView# */{
|
||||
$child_row.toggle(show);
|
||||
}, this);
|
||||
},
|
||||
|
||||
do_hide: function () {
|
||||
this.hidden = true;
|
||||
this._super();
|
||||
}
|
||||
});
|
||||
|
||||
core.view_registry.add('tree', TreeView);
|
||||
|
||||
@@ -5,6 +5,7 @@ var ActionManager = require('web.ActionManager');
|
||||
var core = require('web.core');
|
||||
var crash_manager = require('web.crash_manager');
|
||||
var data = require('web.data');
|
||||
var data_manager = require('web.data_manager');
|
||||
var framework = require('web.framework');
|
||||
var Loading = require('web.Loading');
|
||||
var Menu = require('web.Menu');
|
||||
@@ -16,7 +17,6 @@ var UserMenu = require('web.UserMenu');
|
||||
var utils = require('web.utils');
|
||||
var Widget = require('web.Widget');
|
||||
|
||||
var QWeb = core.qweb;
|
||||
var _t = core._t;
|
||||
|
||||
var WebClient = Widget.extend({
|
||||
@@ -310,7 +310,7 @@ var WebClient = Widget.extend({
|
||||
},
|
||||
on_menu_action: function(options) {
|
||||
var self = this;
|
||||
return this.menu_dm.add(this.rpc("/web/action/load", { action_id: options.action_id }))
|
||||
return this.menu_dm.add(data_manager.load_action(options.action_id))
|
||||
.then(function (result) {
|
||||
return self.action_mutex.exec(function() {
|
||||
if (options.needaction) {
|
||||
|
||||
@@ -171,7 +171,7 @@ odoo.define_section('editor', ['web.ListEditor'], function (test, mock) {
|
||||
});
|
||||
});
|
||||
|
||||
odoo.define_section('list.edition', ['web.data', 'web.ListView'], function (test, mock) {
|
||||
odoo.define_section('list.edition', ['web.data', 'web.ListView', 'web.data_manager'], function (test, mock) {
|
||||
|
||||
function setup () {
|
||||
var records = {};
|
||||
@@ -193,23 +193,12 @@ odoo.define_section('list.edition', ['web.data', 'web.ListView'], function (test
|
||||
}
|
||||
return [];
|
||||
});
|
||||
mock.add('demo:fields_view_get', function () {
|
||||
return {
|
||||
type: 'tree',
|
||||
fields: {
|
||||
a: {type: 'char', string: "A"},
|
||||
b: {type: 'char', string: "B"},
|
||||
c: {type: 'char', string: "C"}
|
||||
},
|
||||
arch: '<tree><field name="a"/><field name="b"/><field name="c"/></tree>',
|
||||
};
|
||||
});
|
||||
mock.add('demo:onchange', function () {
|
||||
return {};
|
||||
});
|
||||
}
|
||||
|
||||
test('newrecord', function (assert, data, ListView) {
|
||||
test('newrecord', function (assert, data, ListView, data_manager) {
|
||||
setup();
|
||||
assert.expect(6);
|
||||
var got_defaults = false;
|
||||
@@ -224,7 +213,16 @@ odoo.define_section('list.edition', ['web.data', 'web.ListView'], function (test
|
||||
});
|
||||
|
||||
var ds = new data.DataSetStatic(null, 'demo', null, [1]);
|
||||
var l = new ListView({}, ds, false, {editable: 'top'});
|
||||
var fields_view = data_manager._postprocess_fvg({
|
||||
type: 'tree',
|
||||
fields: {
|
||||
a: {type: 'char', string: "A"},
|
||||
b: {type: 'char', string: "B"},
|
||||
c: {type: 'char', string: "C"}
|
||||
},
|
||||
arch: '<tree><field name="a"/><field name="b"/><field name="c"/></tree>',
|
||||
});
|
||||
var l = new ListView({}, ds, fields_view, {editable: 'top'});
|
||||
|
||||
var $fix = $( "#qunit-fixture");
|
||||
return l.appendTo($fix)
|
||||
@@ -249,26 +247,25 @@ odoo.define_section('list.edition', ['web.data', 'web.ListView'], function (test
|
||||
});
|
||||
});
|
||||
|
||||
odoo.define_section('list.edition.events', ['web.data', 'web.ListView'], function (test, mock) {
|
||||
|
||||
odoo.define_section('list.edition.events', ['web.data', 'web.ListView', 'web.data_manager'], function (test, mock) {
|
||||
function fields_view_get () {
|
||||
return {
|
||||
type: 'tree',
|
||||
fields: {
|
||||
a: {type: 'char', string: "A"},
|
||||
b: {type: 'char', string: "B"},
|
||||
c: {type: 'char', string: "C"}
|
||||
},
|
||||
arch: '<tree><field name="a"/><field name="b"/><field name="c"/></tree>',
|
||||
};
|
||||
}
|
||||
function setup () {
|
||||
mock.add('demo:read', function () {
|
||||
return [{ id: 1, a: 'foo', b: 'bar', c: 'baz' }];
|
||||
});
|
||||
mock.add('demo:fields_view_get', function () {
|
||||
return {
|
||||
type: 'tree',
|
||||
fields: {
|
||||
a: {type: 'char', string: "A"},
|
||||
b: {type: 'char', string: "B"},
|
||||
c: {type: 'char', string: "C"}
|
||||
},
|
||||
arch: '<tree><field name="a"/><field name="b"/><field name="c"/></tree>',
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
test('edition events',function (assert, data, ListView) {
|
||||
test('edition events',function (assert, data, ListView, data_manager) {
|
||||
setup();
|
||||
assert.expect(4);
|
||||
var ds = new data.DataSetStatic(null, 'demo', null, [1]);
|
||||
@@ -276,7 +273,8 @@ odoo.define_section('list.edition.events', ['web.data', 'web.ListView'], functio
|
||||
counter: 0,
|
||||
onEvent: function (e) { this.counter++; }
|
||||
};
|
||||
var l = new ListView({}, ds, false, {editable: 'top'});
|
||||
var fields_view = data_manager._postprocess_fvg(fields_view_get());
|
||||
var l = new ListView({}, ds, fields_view, {editable: 'top'});
|
||||
l.on('edit:before edit:after', o, o.onEvent);
|
||||
|
||||
var $fix = $( "#qunit-fixture");
|
||||
@@ -293,11 +291,12 @@ odoo.define_section('list.edition.events', ['web.data', 'web.ListView'], functio
|
||||
});
|
||||
});
|
||||
|
||||
test('edition events: cancelling', function (assert, data, ListView) {
|
||||
test('edition events: cancelling', function (assert, data, ListView, data_manager) {
|
||||
setup();
|
||||
var edit_after = false;
|
||||
var ds = new data.DataSetStatic(null, 'demo', null, [1]);
|
||||
var l = new ListView({}, ds, false, {editable: 'top'});
|
||||
var fields_view = data_manager._postprocess_fvg(fields_view_get());
|
||||
var l = new ListView({}, ds, fields_view, {editable: 'top'});
|
||||
l.on('edit:before', {}, function (e) {
|
||||
e.cancel = true;
|
||||
});
|
||||
@@ -321,24 +320,14 @@ odoo.define_section('list.edition.events', ['web.data', 'web.ListView'], functio
|
||||
});
|
||||
});
|
||||
|
||||
odoo.define_section('list.edition.onwrite', ['web.data', 'web.ListView'], function (test, mock) {
|
||||
odoo.define_section('list.edition.onwrite', ['web.data', 'web.ListView', 'web.data_manager'], function (test, mock) {
|
||||
|
||||
test('record-to-read', function (assert, data, ListView) {
|
||||
test('record-to-read', function (assert, data, ListView, data_manager) {
|
||||
assert.expect(4);
|
||||
|
||||
mock.add('demo:onchange', function () {
|
||||
return {};
|
||||
});
|
||||
|
||||
mock.add('demo:fields_view_get', function () {
|
||||
return {
|
||||
type: 'tree',
|
||||
fields: {
|
||||
a: {type: 'char', string: "A"}
|
||||
},
|
||||
arch: '<tree on_write="on_write" colors="red:a == \'foo\'"><field name="a"/></tree>',
|
||||
};
|
||||
});
|
||||
mock.add('demo:read', function (args, kwargs) {
|
||||
if (_.isEmpty(args[0])) {
|
||||
return [];
|
||||
@@ -358,7 +347,14 @@ odoo.define_section('list.edition.onwrite', ['web.data', 'web.ListView'], functi
|
||||
mock.add('demo:on_write', function () { return [42]; });
|
||||
|
||||
var ds = new data.DataSetStatic(null, 'demo', null, []);
|
||||
var l = new ListView({}, ds, false, {editable: 'top'});
|
||||
var fields_view = data_manager._postprocess_fvg({
|
||||
type: 'tree',
|
||||
fields: {
|
||||
a: {type: 'char', string: "A"}
|
||||
},
|
||||
arch: '<tree on_write="on_write" colors="red:a == \'foo\'"><field name="a"/></tree>',
|
||||
});
|
||||
var l = new ListView({}, ds, fields_view, {editable: 'top'});
|
||||
|
||||
var $fix = $( "#qunit-fixture");
|
||||
return l.appendTo($fix)
|
||||
|
||||
@@ -1,17 +1,8 @@
|
||||
odoo.define_section('list.buttons', ['web.ListView', 'web.data'], function (test, mock) {
|
||||
odoo.define_section('list.buttons', ['web.ListView', 'web.data', 'web.data_manager'], function (test, mock) {
|
||||
|
||||
test('record-deletion', function (assert, ListView, data) {
|
||||
test('record-deletion', function (assert, ListView, data, data_manager) {
|
||||
assert.expect(2);
|
||||
|
||||
mock.add('demo:fields_view_get', function () {
|
||||
return {
|
||||
type: 'tree',
|
||||
fields: {
|
||||
a: {type: 'char', string: "A"}
|
||||
},
|
||||
arch: '<tree><field name="a"/><button type="object" name="foo"/></tree>',
|
||||
};
|
||||
});
|
||||
mock.add('demo:read', function (args, kwargs) {
|
||||
if (_.isEqual(args[0], [1, 2, 3])) {
|
||||
return [
|
||||
@@ -29,10 +20,18 @@ odoo.define_section('list.buttons', ['web.ListView', 'web.data'], function (test
|
||||
});
|
||||
mock.add('/web/dataset/call_button', function () { return false; });
|
||||
|
||||
|
||||
var ds = new data.DataSetStatic(null, 'demo', null, [1, 2, 3]);
|
||||
var fields_view = data_manager._postprocess_fvg({
|
||||
type: 'tree',
|
||||
fields: {
|
||||
a: {type: 'char', string: "A"}
|
||||
},
|
||||
arch: '<tree><field name="a"/><button type="object" name="foo"/></tree>',
|
||||
});
|
||||
var list = new ListView({
|
||||
do_action: odoo.testing.noop
|
||||
}, ds, false, {editable: 'top'});
|
||||
}, ds, fields_view, {editable: 'top'});
|
||||
|
||||
var $fix = $( "#qunit-fixture");
|
||||
|
||||
@@ -52,6 +51,5 @@ odoo.define_section('list.buttons', ['web.ListView', 'web.data'], function (test
|
||||
assert.strictEqual($fix.find('table tbody tr[data-id]').length, 2,
|
||||
"should have 2 rows left");
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
@@ -156,28 +156,19 @@ odoo.define_section('search.query', ['web.SearchView'], function (test) {
|
||||
* @return {instance.web.SearchView}
|
||||
*/
|
||||
|
||||
function makeSearchView (test, dummy_widget_attributes, defaults, options) {
|
||||
function makeSearchView (test, dummy_widget_attributes, defaults, options, raw_fields_view) {
|
||||
var core = test.deps['web.core'];
|
||||
var search_inputs = test.deps['web.search_inputs'];
|
||||
var data = test.deps['web.data'];
|
||||
var data_manager = test.deps['web.data_manager'];
|
||||
var SearchView = test.deps['web.SearchView'];
|
||||
|
||||
var mock = test.mock;
|
||||
var assert = test.assert
|
||||
var assert = test.assert;
|
||||
|
||||
var DummyWidget = search_inputs.Field.extend(dummy_widget_attributes || {});
|
||||
core.search_widgets_registry.add('dummy', DummyWidget);
|
||||
|
||||
mock.add('dummy.model:fields_view_get', function () {
|
||||
return {
|
||||
type: 'search',
|
||||
fields: {
|
||||
dummy: {type: 'char', string: "Dummy", searchable: true}
|
||||
},
|
||||
arch: '<search><field name="dummy" widget="dummy"/></search>'
|
||||
};
|
||||
}, true);
|
||||
|
||||
mock.add('ir.filters:get_filters', function () {
|
||||
return [];
|
||||
});
|
||||
@@ -188,16 +179,22 @@ function makeSearchView (test, dummy_widget_attributes, defaults, options) {
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
// instance.client = { action_manager: { inner_action: undefined } };
|
||||
|
||||
var dataset = new data.DataSet(null, 'dummy.model');
|
||||
|
||||
var mock_parent = {getParent: function () {return null;}};
|
||||
var mock_parent = {
|
||||
getParent: function () { return null; },
|
||||
};
|
||||
|
||||
options = _.defaults(options || {}, {$buttons: $('<div>')});
|
||||
|
||||
var view = new SearchView(mock_parent, dataset, false, defaults, options);
|
||||
options.search_defaults = defaults;
|
||||
var fields_view = data_manager._postprocess_fvg(raw_fields_view || {
|
||||
type: 'search',
|
||||
fields: {
|
||||
dummy: {type: 'char', string: "Dummy", searchable: true}
|
||||
},
|
||||
arch: '<search><field name="dummy" widget="dummy"/></search>'
|
||||
});
|
||||
var view = new SearchView(mock_parent, dataset, fields_view, options);
|
||||
|
||||
view.on('invalid_search', this, function () {
|
||||
assert.ok(false, JSON.stringify([].slice(arguments)));
|
||||
@@ -205,7 +202,7 @@ function makeSearchView (test, dummy_widget_attributes, defaults, options) {
|
||||
return view;
|
||||
};
|
||||
|
||||
odoo.define_section('search.defaults', ['web.search_inputs', 'web.SearchView', 'web.core', 'web.data'], function (test, mock) {
|
||||
odoo.define_section('search.defaults', ['web.search_inputs', 'web.SearchView', 'web.core', 'web.data', 'web.data_manager'], function (test, mock) {
|
||||
test('calling', function (assert) {
|
||||
assert.expect(2);
|
||||
var defaults_called = false;
|
||||
@@ -431,7 +428,7 @@ odoo.define_section('search.defaults', ['web.search_inputs', 'web.SearchView', '
|
||||
|
||||
});
|
||||
|
||||
odoo.define_section('search.completions', ['web.search_inputs', 'web.SearchView', 'web.core', 'web.data'], function (test, mock) {
|
||||
odoo.define_section('search.completions', ['web.search_inputs', 'web.SearchView', 'web.core', 'web.data', 'web.data_manager'], function (test, mock) {
|
||||
|
||||
test('calling', function (assert) {
|
||||
assert.expect(4);
|
||||
@@ -851,7 +848,7 @@ odoo.define_section('search.completions', ['web.search_inputs', 'web.SearchView'
|
||||
});
|
||||
});
|
||||
|
||||
odoo.define_section('search.serialization', ['web.search_inputs', 'web.SearchView', 'web.core', 'web.data'], function (test, mock) {
|
||||
odoo.define_section('search.serialization', ['web.search_inputs', 'web.SearchView', 'web.core', 'web.data', 'web.data_manager'], function (test, mock) {
|
||||
|
||||
test('No facet, no call', function (assert, search_inputs, SearchView, core) {
|
||||
assert.expect(6);
|
||||
@@ -1191,7 +1188,7 @@ odoo.define_section('search.serialization', ['web.search_inputs', 'web.SearchVie
|
||||
});
|
||||
});
|
||||
|
||||
odoo.define_section('search.serialization', ['web.search_inputs', 'web.SearchView', 'web.core', 'web.data'], function (test, mock) {
|
||||
odoo.define_section('search.serialization', ['web.search_inputs', 'web.SearchView', 'web.core', 'web.data', 'web.data_manager'], function (test, mock) {
|
||||
|
||||
test('is-drawn', function (assert) {
|
||||
assert.expect(3);
|
||||
@@ -1210,26 +1207,23 @@ odoo.define_section('search.serialization', ['web.search_inputs', 'web.SearchVie
|
||||
|
||||
});
|
||||
|
||||
odoo.define_section('search.filters', ['web.search_inputs', 'web.SearchView', 'web.core', 'web.data'], function (test, mock) {
|
||||
function setup () {
|
||||
mock.add('dummy.model:fields_view_get', function () {
|
||||
// view with a single group of filters
|
||||
return {
|
||||
type: 'search',
|
||||
fields: {},
|
||||
arch: '<search>' +
|
||||
'<filter string="Foo1" domain="[ [\'foo\', \'=\', \'1\'] ]"/>' +
|
||||
'<filter name="foo2" string="Foo2" domain="[ [\'foo\', \'=\', \'2\'] ]"/>' +
|
||||
'<filter string="Foo3" domain="[ [\'foo\', \'=\', \'3\'] ]"/>' +
|
||||
'</search>',
|
||||
};
|
||||
});
|
||||
odoo.define_section('search.filters', ['web.search_inputs', 'web.SearchView', 'web.core', 'web.data', 'web.data_manager'], function (test, mock) {
|
||||
function fields_view_get () {
|
||||
// view with a single group of filters
|
||||
return {
|
||||
type: 'search',
|
||||
fields: {},
|
||||
arch: '<search>' +
|
||||
'<filter string="Foo1" domain="[ [\'foo\', \'=\', \'1\'] ]"/>' +
|
||||
'<filter name="foo2" string="Foo2" domain="[ [\'foo\', \'=\', \'2\'] ]"/>' +
|
||||
'<filter string="Foo3" domain="[ [\'foo\', \'=\', \'3\'] ]"/>' +
|
||||
'</search>',
|
||||
};
|
||||
}
|
||||
|
||||
test('drawn', function (assert) {
|
||||
setup();
|
||||
assert.expect(3);
|
||||
var view = makeSearchView(this);
|
||||
var view = makeSearchView(this, {}, {}, {}, fields_view_get());
|
||||
var $fix = $('#qunit-fixture');
|
||||
|
||||
return view.appendTo($fix)
|
||||
@@ -1237,10 +1231,9 @@ odoo.define_section('search.filters', ['web.search_inputs', 'web.SearchView', 'w
|
||||
var $filters = view.$buttons.find('.filters-menu li'),
|
||||
$favorites = view.$buttons.find('.o_favorites_menu li'),
|
||||
$groupby = view.$buttons.find('.group-by-menu li');
|
||||
// 3 filters, 1 separator, 1 button add filter,
|
||||
// 1 filter condition menu, 1 apply button
|
||||
assert.equal($filters.length, 7,
|
||||
'filter menu should have 7 elements total');
|
||||
// 3 filters, 1 separator, 1 button add filter, 1 apply button
|
||||
assert.equal($filters.length, 6,
|
||||
'filter menu should have 6 elements total');
|
||||
|
||||
// 1 divider, 1 save search button, 1 text input, 2 checkboxes,
|
||||
// 1 save button, 3 add to dashboard things (a, input, button)
|
||||
@@ -1249,9 +1242,9 @@ odoo.define_section('search.filters', ['web.search_inputs', 'web.SearchView', 'w
|
||||
// assert.equal($favorites.length, 9,
|
||||
// "favorites menu should have 9 elements");
|
||||
|
||||
// 1 divider, 1 add custom group button, 1 select groupby, 1 apply button,
|
||||
assert.equal($groupby.length, 4,
|
||||
"groupby menu should have 4 element");
|
||||
// 1 divider, 1 add custom group button
|
||||
assert.equal($groupby.length, 2,
|
||||
"groupby menu should have 2 element");
|
||||
assert.equal(_.str.strip($filters.find('a')[0].textContent), "Foo1",
|
||||
"Text content of first filter option should match filter string");
|
||||
});
|
||||
@@ -1259,10 +1252,9 @@ odoo.define_section('search.filters', ['web.search_inputs', 'web.SearchView', 'w
|
||||
|
||||
test('click adding from empty query', function (assert, search_inputs) {
|
||||
assert.expect(4);
|
||||
setup();
|
||||
var Filter = search_inputs.Filter;
|
||||
|
||||
var view = makeSearchView(this);
|
||||
var view = makeSearchView(this, {}, {}, {}, fields_view_get());
|
||||
var $fix = $('#qunit-fixture');
|
||||
return view.appendTo($fix)
|
||||
.done(function () {
|
||||
@@ -1281,8 +1273,7 @@ odoo.define_section('search.filters', ['web.search_inputs', 'web.SearchView', 'w
|
||||
|
||||
test('click adding from existing query', function (assert) {
|
||||
assert.expect(4);
|
||||
setup();
|
||||
var view = makeSearchView(this, {}, {foo2: true});
|
||||
var view = makeSearchView(this, {}, {foo2: true}, {}, fields_view_get());
|
||||
|
||||
var $fix = $('#qunit-fixture');
|
||||
return view.appendTo($fix)
|
||||
@@ -1304,10 +1295,9 @@ odoo.define_section('search.filters', ['web.search_inputs', 'web.SearchView', 'w
|
||||
|
||||
test('click removing from query', function (assert) {
|
||||
assert.expect(4);
|
||||
setup();
|
||||
|
||||
var calls = 0;
|
||||
var view = makeSearchView(this, {}, {foo2: true});
|
||||
var view = makeSearchView(this, {}, {foo2: true}, {}, fields_view_get());
|
||||
view.on('search_data', null, function () {
|
||||
++calls;
|
||||
});
|
||||
@@ -1325,24 +1315,21 @@ odoo.define_section('search.filters', ['web.search_inputs', 'web.SearchView', 'w
|
||||
});
|
||||
});
|
||||
|
||||
odoo.define_section('search.groupby', ['web.search_inputs', 'web.SearchView', 'web.core', 'web.data'], function (test, mock) {
|
||||
odoo.define_section('search.groupby', ['web.search_inputs', 'web.SearchView', 'web.core', 'web.data', 'web.data_manager'], function (test, mock) {
|
||||
|
||||
test('basic', ['web.FavoriteMenu', 'web.FilterMenu', 'web.GroupByMenu'], function (assert, search_inputs, SearchView, core, data, FavoriteMenu, FilterMenu, GroupByMenu) {
|
||||
test('basic', ['web.FavoriteMenu', 'web.FilterMenu', 'web.GroupByMenu'], function (assert, search_inputs, SearchView, core, data, data_manager, FavoriteMenu, FilterMenu, GroupByMenu) {
|
||||
assert.expect(12);
|
||||
mock.add('dummy.model:fields_view_get', function () {
|
||||
return {
|
||||
type: 'search',
|
||||
fields: {},
|
||||
arch: [
|
||||
'<search>',
|
||||
'<filter string="Foo" context="{\'group_by\': \'foo\'}"/>',
|
||||
'<filter string="Bar" context="{\'group_by\': \'bar\'}"/>',
|
||||
'<filter string="Baz" context="{\'group_by\': \'baz\'}"/>',
|
||||
'</search>'
|
||||
].join(''),
|
||||
};
|
||||
var view = makeSearchView(this, {}, {}, {}, {
|
||||
type: 'search',
|
||||
fields: {},
|
||||
arch: [
|
||||
'<search>',
|
||||
'<filter string="Foo" context="{\'group_by\': \'foo\'}"/>',
|
||||
'<filter string="Bar" context="{\'group_by\': \'bar\'}"/>',
|
||||
'<filter string="Baz" context="{\'group_by\': \'baz\'}"/>',
|
||||
'</search>'
|
||||
].join(''),
|
||||
});
|
||||
var view = makeSearchView(this);
|
||||
var $fix = $('#qunit-fixture');
|
||||
return view.appendTo($fix)
|
||||
.done(function () {
|
||||
@@ -1378,22 +1365,19 @@ odoo.define_section('search.groupby', ['web.search_inputs', 'web.SearchView', 'w
|
||||
|
||||
test('unified multiple groupby groups', function (assert, search_inputs, SearchView, core, data) {
|
||||
assert.expect(3);
|
||||
mock.add('dummy.model:fields_view_get', function () {
|
||||
return {
|
||||
type: 'search',
|
||||
fields: {},
|
||||
arch: [
|
||||
'<search>',
|
||||
'<filter string="Foo" context="{\'group_by\': \'foo\'}"/>',
|
||||
'<separator/>',
|
||||
'<filter string="Bar" context="{\'group_by\': \'bar\'}"/>',
|
||||
'<separator/>',
|
||||
'<filter string="Baz" context="{\'group_by\': \'baz\'}"/>',
|
||||
'</search>'
|
||||
].join(''),
|
||||
};
|
||||
var view = makeSearchView(this, {}, {}, {}, {
|
||||
type: 'search',
|
||||
fields: {},
|
||||
arch: [
|
||||
'<search>',
|
||||
'<filter string="Foo" context="{\'group_by\': \'foo\'}"/>',
|
||||
'<separator/>',
|
||||
'<filter string="Bar" context="{\'group_by\': \'bar\'}"/>',
|
||||
'<separator/>',
|
||||
'<filter string="Baz" context="{\'group_by\': \'baz\'}"/>',
|
||||
'</search>'
|
||||
].join(''),
|
||||
});
|
||||
var view = makeSearchView(this);
|
||||
var $fix = $('#qunit-fixture');
|
||||
|
||||
return view.appendTo($fix)
|
||||
@@ -1415,7 +1399,7 @@ odoo.define_section('search.groupby', ['web.search_inputs', 'web.SearchView', 'w
|
||||
});
|
||||
});
|
||||
|
||||
odoo.define_section('search.filters.saved', ['web.search_inputs', 'web.SearchView', 'web.core', 'web.data'], function (test, mock) {
|
||||
odoo.define_section('search.filters.saved', ['web.search_inputs', 'web.SearchView', 'web.core', 'web.data', 'web.data_manager'], function (test, mock) {
|
||||
|
||||
test('checkboxing', function (assert) {
|
||||
assert.expect(6);
|
||||
@@ -1531,7 +1515,7 @@ odoo.define_section('search.filters.saved', ['web.search_inputs', 'web.SearchVie
|
||||
});
|
||||
});
|
||||
|
||||
odoo.define_section('search.advanced', ['web.search_inputs', 'web.SearchView', 'web.core', 'web.data'], function (test, mock) {
|
||||
odoo.define_section('search.advanced', ['web.search_inputs', 'web.SearchView', 'web.core', 'web.data', 'web.data_manager'], function (test, mock) {
|
||||
|
||||
test('single-advanced', function (assert) {
|
||||
assert.expect(6);
|
||||
|
||||
@@ -115,6 +115,8 @@
|
||||
<script type="text/javascript" src="/web/static/src/js/framework/misc.js"></script>
|
||||
<script type="text/javascript" src="/web/static/src/js/framework/session_instance.js"></script>
|
||||
<script type="text/javascript" src="/web/static/src/js/framework/crash_manager.js"></script>
|
||||
<script type="text/javascript" src="/web/static/src/js/framework/data_manager.js"></script>
|
||||
<script type="text/javascript" src="/web/static/src/js/services/data_manager.js"></script>
|
||||
<script type="text/javascript" src="/web/static/src/js/widgets/change_password.js"></script>
|
||||
<script type="text/javascript" src="/web/static/src/js/widgets/debug_manager.js"></script>
|
||||
<script type="text/javascript" src="/web/static/src/js/widgets/data_export.js"></script>
|
||||
|
||||
@@ -61,65 +61,32 @@ function isNullOrUndef(value) {
|
||||
}
|
||||
|
||||
var CalendarView = View.extend({
|
||||
template: "CalendarView",
|
||||
defaults: _.extend({}, View.prototype.defaults, {
|
||||
confirm_on_delete: true,
|
||||
}),
|
||||
display_name: _lt('Calendar'),
|
||||
icon: 'fa-calendar',
|
||||
quick_create_instance: widgets.QuickCreate,
|
||||
template: "CalendarView",
|
||||
|
||||
init: function (parent, dataset, view_id, options) {
|
||||
this._super(parent);
|
||||
this.ready = $.Deferred();
|
||||
this.set_default_options(options);
|
||||
this.dataset = dataset;
|
||||
this.model = dataset.model;
|
||||
this.fields_view = {};
|
||||
this.view_id = view_id;
|
||||
this.view_type = 'calendar';
|
||||
init: function () {
|
||||
this._super.apply(this, arguments);
|
||||
this.color_map = {};
|
||||
this.range_start = null;
|
||||
this.range_stop = null;
|
||||
this.selected_filters = [];
|
||||
this.info_fields = [];
|
||||
|
||||
this.title = (this.options.action)? this.options.action.name : '';
|
||||
|
||||
this.shown = $.Deferred();
|
||||
},
|
||||
|
||||
set_default_options: function(options) {
|
||||
this._super(options);
|
||||
_.defaults(this.options, {
|
||||
confirm_on_delete: true
|
||||
});
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
if (this.$calendar) {
|
||||
this.$calendar.fullCalendar('destroy');
|
||||
}
|
||||
if (this.$small_calendar) {
|
||||
this.$small_calendar.datepicker('destroy');
|
||||
}
|
||||
this._super.apply(this, arguments);
|
||||
},
|
||||
|
||||
view_loading: function (fv) {
|
||||
/* xml view calendar options */
|
||||
var attrs = fv.arch.attrs,
|
||||
self = this;
|
||||
this.fields_view = fv;
|
||||
this.$calendar = this.$(".o_calendar_widget");
|
||||
|
||||
this.info_fields = [];
|
||||
|
||||
var attrs = this.fields_view.arch.attrs;
|
||||
if (!attrs.date_start) {
|
||||
throw new Error(_t("Calendar view has not defined 'date_start' attribute."));
|
||||
}
|
||||
|
||||
this.$el.addClass(attrs['class']);
|
||||
|
||||
this.name = fv.name || attrs.string;
|
||||
this.view_id = fv.view_id;
|
||||
|
||||
this.fields = this.fields_view.fields;
|
||||
this.name = this.fields_view.name || attrs.string;
|
||||
this.mode = attrs.mode; // one of month, week or day
|
||||
this.date_start = attrs.date_start; // Field name of starting date field
|
||||
this.date_delay = attrs.date_delay; // duration
|
||||
@@ -129,7 +96,7 @@ var CalendarView = View.extend({
|
||||
this.attendee_people = attrs.attendee;
|
||||
|
||||
// Check whether the date field is editable (i.e. if the events can be dragged and dropped)
|
||||
this.editable = !this.options.read_only_mode && !this.fields_view.fields[this.date_start].readonly;
|
||||
this.editable = !this.options.read_only_mode && !this.fields[this.date_start].readonly;
|
||||
|
||||
//if quick_add = False, we don't allow quick_add
|
||||
//if quick_add = not specified in view, we use the default quick_create_instance
|
||||
@@ -155,7 +122,7 @@ var CalendarView = View.extend({
|
||||
this.colorIsAttendee = !(isNullOrUndef(attrs.color_is_attendee) || !_.str.toBoolElse(attrs.color_is_attendee, true));
|
||||
|
||||
// if we have not sidebar, (eg: Dashboard), we don't use the filter "coworkers"
|
||||
if (isNullOrUndef(self.options.sidebar)) {
|
||||
if (isNullOrUndef(this.options.sidebar)) {
|
||||
this.useContacts = false;
|
||||
this.colorIsAttendee = false;
|
||||
this.attendee_people = undefined;
|
||||
@@ -203,32 +170,38 @@ var CalendarView = View.extend({
|
||||
}
|
||||
}
|
||||
|
||||
this.fields = fv.fields;
|
||||
|
||||
for (var fld = 0; fld < fv.arch.children.length; fld++) {
|
||||
this.info_fields.push(fv.arch.children[fld].attrs.name);
|
||||
for (var fld = 0; fld < this.fields_view.arch.children.length; fld++) {
|
||||
this.info_fields.push(this.fields_view.arch.children[fld].attrs.name);
|
||||
}
|
||||
|
||||
self.shown.done(this._do_show_init.bind(this));
|
||||
var edit_check = new Model(this.dataset.model)
|
||||
.call("check_access_rights", ["write", false])
|
||||
.then(function (write_right) {
|
||||
self.write_right = write_right;
|
||||
});
|
||||
var init = new Model(this.dataset.model)
|
||||
.call("check_access_rights", ["create", false])
|
||||
.then(function (create_right) {
|
||||
self.create_right = create_right;
|
||||
self.ready.resolve();
|
||||
self.trigger('calendar_view_loaded', fv);
|
||||
});
|
||||
return $.when(edit_check, init);
|
||||
},
|
||||
_do_show_init: function () {
|
||||
willStart: function () {
|
||||
var self = this;
|
||||
var write_def = this.dataset.call("check_access_rights", ["write", false]);
|
||||
var create_def = this.dataset.call("check_access_rights", ["create", false]);
|
||||
return $.when(write_def, create_def, this._super()).then(function (write, create) {
|
||||
self.write_right = write;
|
||||
self.create_right = create;
|
||||
});
|
||||
},
|
||||
start: function () {
|
||||
this.$calendar = this.$(".o_calendar_widget");
|
||||
this.$el.addClass(this.fields_view.arch.attrs.class);
|
||||
this.shown.done(this._do_show_init.bind(this));
|
||||
return this._super();
|
||||
},
|
||||
destroy: function() {
|
||||
if (this.$calendar) {
|
||||
this.$calendar.fullCalendar('destroy');
|
||||
}
|
||||
if (this.$small_calendar) {
|
||||
this.$small_calendar.datepicker('destroy');
|
||||
}
|
||||
this._super.apply(this, arguments);
|
||||
},
|
||||
|
||||
_do_show_init: function () {
|
||||
this.init_calendar().then(function() {
|
||||
$(window).trigger('resize');
|
||||
self.trigger('calendar_view_loaded', self.fields_view);
|
||||
});
|
||||
},
|
||||
/**
|
||||
@@ -876,9 +849,9 @@ var CalendarView = View.extend({
|
||||
}
|
||||
}
|
||||
else {
|
||||
var dialog = new form_common.FormViewDialog(this, {
|
||||
res_model: this.dataset.model,
|
||||
res_id: parseInt(id).toString() == id ? parseInt(id) : id,
|
||||
new form_common.FormViewDialog(this, {
|
||||
res_model: this.model,
|
||||
res_id: parseInt(id).toString() === id ? parseInt(id) : id,
|
||||
context: this.dataset.get_context(),
|
||||
title: title,
|
||||
view_id: +this.open_popup_action,
|
||||
|
||||
@@ -2,7 +2,6 @@ odoo.define('web_calendar.widgets', function(require) {
|
||||
"use strict";
|
||||
|
||||
var core = require('web.core');
|
||||
var data = require('web.data');
|
||||
var Dialog = require('web.Dialog');
|
||||
var form_common = require('web.form_common');
|
||||
var Widget = require('web.Widget');
|
||||
@@ -69,12 +68,12 @@ var QuickCreate = Dialog.extend({
|
||||
});
|
||||
|
||||
this.$input = this.$('input').keyup(function enterHandler (e) {
|
||||
if(e.keyCode == $.ui.keyCode.ENTER) {
|
||||
if(e.keyCode === $.ui.keyCode.ENTER) {
|
||||
self.$input.off('keyup', enterHandler);
|
||||
if (!self.quick_add()){
|
||||
self.$input.on('keyup', enterHandler);
|
||||
}
|
||||
} else if (e.keyCode == $.ui.keyCode.ESCAPE && self._buttons) {
|
||||
} else if (e.keyCode === $.ui.keyCode.ESCAPE && self._buttons) {
|
||||
self.close();
|
||||
}
|
||||
});
|
||||
@@ -114,20 +113,6 @@ var QuickCreate = Dialog.extend({
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Show full form popup
|
||||
*/
|
||||
get_form_popup_infos: function() {
|
||||
var parent = this.getParent();
|
||||
var infos = {
|
||||
view_id: false,
|
||||
title: this.name,
|
||||
};
|
||||
if (!_.isUndefined(parent) && !(_.isUndefined(parent.ViewManager))) {
|
||||
infos.view_id = parent.ViewManager.get_view_id('form');
|
||||
}
|
||||
return infos;
|
||||
},
|
||||
slow_create: function(_data) {
|
||||
//if all day, we could reset time to display 00:00:00
|
||||
|
||||
@@ -140,26 +125,21 @@ var QuickCreate = Dialog.extend({
|
||||
defaults['default_' + field_name] = val;
|
||||
});
|
||||
|
||||
var pop_infos = self.get_form_popup_infos();
|
||||
var context = new data.CompoundContext(this.dataset.context, defaults);
|
||||
var pop = new form_common.FormViewDialog(this, {
|
||||
res_model: this.dataset.model,
|
||||
context: this.dataset.get_context(defaults),
|
||||
title: this.get_title(),
|
||||
disable_multiple_selection: true,
|
||||
view_id: pop_infos.view_id,
|
||||
// Ensuring we use ``self.dataset`` and DO NOT create a new one.
|
||||
create_function: function(data, options) {
|
||||
return self.dataset.create(data, options).done(function(r) {
|
||||
}).fail(function (r, event) {
|
||||
return self.dataset.create(data, options).fail(function (r) {
|
||||
if (!r.data.message) { //else manage by openerp
|
||||
throw new Error(r);
|
||||
}
|
||||
});
|
||||
},
|
||||
read_function: function(id, fields, options) {
|
||||
return self.dataset.read_ids.apply(self.dataset, arguments).done(function() {
|
||||
}).fail(function (r, event) {
|
||||
read_function: function() {
|
||||
return self.dataset.read_ids.apply(self.dataset, arguments).fail(function (r) {
|
||||
if (!r.data.message) { //else manage by openerp
|
||||
throw new Error(r);
|
||||
}
|
||||
@@ -171,7 +151,7 @@ var QuickCreate = Dialog.extend({
|
||||
def.resolve();
|
||||
}
|
||||
});
|
||||
pop.on('create_completed', self, function(id) {
|
||||
pop.on('create_completed', self, function() {
|
||||
created = true;
|
||||
self.trigger('slowadded');
|
||||
});
|
||||
@@ -231,13 +211,12 @@ var SidebarFilter = Widget.extend({
|
||||
this.$el.html(QWeb.render('CalendarView.sidebar.responsible', { filters: filters }));
|
||||
},
|
||||
filter_click: function(e) {
|
||||
var self = this;
|
||||
if (self.view.all_filters[0] && e.target.value == self.view.all_filters[0].value) {
|
||||
self.view.all_filters[0].is_checked = e.target.checked;
|
||||
if (this.view.all_filters[0] && e.target.value === this.view.all_filters[0].value) {
|
||||
this.view.all_filters[0].is_checked = e.target.checked;
|
||||
} else {
|
||||
self.view.all_filters[e.target.value].is_checked = e.target.checked;
|
||||
this.view.all_filters[e.target.value].is_checked = e.target.checked;
|
||||
}
|
||||
self.view.$calendar.fullCalendar('refetchEvents');
|
||||
this.view.$calendar.fullCalendar('refetchEvents');
|
||||
},
|
||||
select_previous: function(e) {
|
||||
$(e.target).siblings('input').trigger('click');
|
||||
|
||||
@@ -18,45 +18,17 @@ var QWeb = core.qweb;
|
||||
var DiagramView = View.extend({
|
||||
display_name: _lt('Diagram'),
|
||||
icon: 'fa-code-fork',
|
||||
view_type: 'diagram',
|
||||
searchable: false,
|
||||
multi_record: false,
|
||||
init: function(parent, dataset, view_id, options) {
|
||||
this._super(parent);
|
||||
this.set_default_options(options);
|
||||
this.view_manager = parent;
|
||||
this.dataset = dataset;
|
||||
this.model = this.dataset.model;
|
||||
this.view_id = view_id;
|
||||
searchable: false,
|
||||
|
||||
init: function() {
|
||||
this._super.apply(this, arguments);
|
||||
this.domain = this.dataset._domain || [];
|
||||
this.context = {};
|
||||
this.ids = this.dataset.ids;
|
||||
},
|
||||
|
||||
view_loading: function(r) {
|
||||
if (window.Raphael) {
|
||||
return this.load_diagram(r);
|
||||
if (this.ids && this.ids.length) {
|
||||
this.id = this.ids[this.dataset.index || 0];
|
||||
}
|
||||
var self = this;
|
||||
return $.when(
|
||||
ajax.loadJS('/web_diagram/static/lib/js/jquery.mousewheel.js'),
|
||||
ajax.loadJS('/web_diagram/static/lib/js/raphael.js')).then(function () {
|
||||
return self.load_diagram(r);
|
||||
});
|
||||
},
|
||||
|
||||
toTitleCase: function(str) {
|
||||
return str.replace(/\w\S*/g, function(txt){return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();});
|
||||
},
|
||||
|
||||
load_diagram: function(result) {
|
||||
var self = this;
|
||||
if(this.ids && this.ids.length) {
|
||||
this.id = this.ids[self.dataset.index || 0];
|
||||
}
|
||||
|
||||
this.fields_view = result;
|
||||
this.view_id = this.fields_view.view_id;
|
||||
this.fields = this.fields_view.fields;
|
||||
this.nodes = this.fields_view.arch.children[0];
|
||||
this.connectors = this.fields_view.arch.children[1];
|
||||
@@ -65,19 +37,35 @@ var DiagramView = View.extend({
|
||||
this.labels = _.filter(this.fields_view.arch.children, function(label) {
|
||||
return label.tag === "label";
|
||||
});
|
||||
},
|
||||
|
||||
this.$el.html(QWeb.render("DiagramView", {'widget': self}));
|
||||
this.$el.addClass('o_diagram_view').addClass(this.fields_view.arch.attrs['class']);
|
||||
willStart: function() {
|
||||
if (window.Raphael) {
|
||||
return $.when();
|
||||
}
|
||||
return $.when(
|
||||
ajax.loadJS('/web_diagram/static/lib/js/jquery.mousewheel.js'),
|
||||
ajax.loadJS('/web_diagram/static/lib/js/raphael.js')
|
||||
);
|
||||
},
|
||||
start: function() {
|
||||
var self = this;
|
||||
this.$el.html(QWeb.render("DiagramView", {'widget': this}));
|
||||
this.$el.addClass('o_diagram_view').addClass(this.fields_view.arch.attrs.class);
|
||||
|
||||
_.each(self.labels,function(label){
|
||||
_.each(this.labels,function(label){
|
||||
self.$('.o_diagram_header').append($('<span>').html(label.attrs.string));
|
||||
});
|
||||
|
||||
|
||||
var diagram_info;
|
||||
if(this.id) {
|
||||
self.get_diagram_info();
|
||||
diagram_info = this.get_diagram_info();
|
||||
}
|
||||
this.trigger('diagram_view_loaded', result);
|
||||
return $.when(this._super(), diagram_info);
|
||||
},
|
||||
|
||||
toTitleCase: function(str) {
|
||||
return str.replace(/\w\S*/g, function(txt){return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();});
|
||||
},
|
||||
|
||||
get_diagram_info: function() {
|
||||
@@ -112,7 +100,7 @@ var DiagramView = View.extend({
|
||||
params.connectors_fields.push(self.fields[conn.attrs.name]['string']|| this.toTitleCase(conn.attrs.name));
|
||||
params.connectors.push(conn.attrs.name);
|
||||
});
|
||||
this.rpc(
|
||||
return this.rpc(
|
||||
'/web_diagram/diagram/get_diagram_info',params).done(function(result) {
|
||||
self.draw_diagram(result);
|
||||
}
|
||||
@@ -277,7 +265,7 @@ var DiagramView = View.extend({
|
||||
var title = _t('Activity');
|
||||
var pop = new form_common.FormViewDialog(self, {
|
||||
res_model: self.node,
|
||||
domain: self.dataset.domain,
|
||||
domain: self.domain,
|
||||
context: self.context || self.dataset.context,
|
||||
title: _t("Create:") + title,
|
||||
disable_multiple_selection: true,
|
||||
@@ -321,7 +309,7 @@ var DiagramView = View.extend({
|
||||
var title = _t('Transition');
|
||||
var pop = new form_common.FormViewDialog(self, {
|
||||
res_model: self.connector,
|
||||
domain: this.dataset.domain,
|
||||
domain: this.domain,
|
||||
context: this.context || this.dataset.context,
|
||||
title: _t("Create:") + title,
|
||||
disable_multiple_selection: true,
|
||||
|
||||
@@ -26,11 +26,6 @@ var fields_registry = kanban_widgets.registry;
|
||||
var KanbanView = View.extend({
|
||||
accesskey: "K",
|
||||
className: "o_kanban_view",
|
||||
display_name: _lt("Kanban"),
|
||||
icon: 'fa-th-large',
|
||||
mobile_friendly: true,
|
||||
view_type: "kanban",
|
||||
|
||||
custom_events: {
|
||||
'kanban_record_open': 'open_record',
|
||||
'kanban_record_edit': 'edit_record',
|
||||
@@ -49,27 +44,30 @@ var KanbanView = View.extend({
|
||||
'kanban_load_more': 'load_more',
|
||||
'kanban_call_method': 'call_method',
|
||||
},
|
||||
defaults: _.extend(View.prototype.defaults, {
|
||||
quick_creatable: true,
|
||||
creatable: true,
|
||||
create_text: undefined,
|
||||
read_only_mode: false,
|
||||
confirm_on_delete: true,
|
||||
}),
|
||||
display_name: _lt("Kanban"),
|
||||
icon: 'fa-th-large',
|
||||
mobile_friendly: true,
|
||||
|
||||
init: function (parent, dataset, view_id, options) {
|
||||
this._super(parent, dataset, view_id, options);
|
||||
_.defaults(this.options, {
|
||||
"quick_creatable": true,
|
||||
"creatable": true,
|
||||
"create_text": undefined,
|
||||
"read_only_mode": false,
|
||||
"confirm_on_delete": true,
|
||||
});
|
||||
init: function () {
|
||||
this._super.apply(this, arguments);
|
||||
|
||||
// qweb setup
|
||||
this.qweb = new QWeb2.Engine();
|
||||
this.qweb.debug = session.debug;
|
||||
this.qweb.default_dict = _.clone(QWeb.default_dict);
|
||||
|
||||
this.model = this.dataset.model;
|
||||
this.limit = options.limit || 40;
|
||||
this.limit = this.options.limit || 40;
|
||||
this.fields_keys = _.keys(this.fields_view.fields);
|
||||
this.grouped = undefined;
|
||||
this.group_by_field = undefined;
|
||||
this.default_group_by = undefined;
|
||||
this.default_group_by = this.fields_view.arch.attrs.default_group_by;
|
||||
this.grouped_by_m2o = undefined;
|
||||
this.relation = undefined;
|
||||
this.is_empty = undefined;
|
||||
@@ -77,39 +75,39 @@ var KanbanView = View.extend({
|
||||
this.m2m_context = {};
|
||||
this.widgets = [];
|
||||
this.data = undefined;
|
||||
this.model = dataset.model;
|
||||
this.quick_creatable = options.quick_creatable;
|
||||
this.no_content_msg = options.action && (options.action.get_empty_list_help || options.action.help);
|
||||
this.quick_creatable = this.options.quick_creatable;
|
||||
this.no_content_msg = this.options.action &&
|
||||
(this.options.action.get_empty_list_help || this.options.action.help);
|
||||
this.search_orderer = new utils.DropMisordered();
|
||||
},
|
||||
|
||||
view_loading: function(fvg) {
|
||||
this.$el.addClass(fvg.arch.attrs.class);
|
||||
this.fields_view = fvg;
|
||||
this.default_group_by = fvg.arch.attrs.default_group_by;
|
||||
|
||||
this.fields_keys = _.keys(this.fields_view.fields);
|
||||
|
||||
// use default order if defined in xml description
|
||||
var default_order = this.fields_view.arch.attrs.default_order;
|
||||
if (!this.dataset._sort.length && default_order) {
|
||||
this.dataset.set_sort(default_order.split(','));
|
||||
}
|
||||
},
|
||||
|
||||
willStart: function() {
|
||||
// add qweb templates
|
||||
for (var i=0, ii=this.fields_view.arch.children.length; i < ii; i++) {
|
||||
var child = this.fields_view.arch.children[i];
|
||||
if (child.tag === "templates") {
|
||||
transform_qweb_template(child, fvg, this.many2manys);
|
||||
transform_qweb_template(child, this.fields_view, this.many2manys);
|
||||
this.qweb.add_template(utils.json_node_to_xml(child));
|
||||
break;
|
||||
} else if (child.tag === 'field') {
|
||||
var ftype = child.attrs.widget || this.fields_view.fields[child.attrs.name].type;
|
||||
if(ftype == "many2many" && "context" in child.attrs) {
|
||||
if(ftype === "many2many" && "context" in child.attrs) {
|
||||
this.m2m_context[child.attrs.name] = child.attrs.context;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.trigger('kanban_view_loaded');
|
||||
return this._super();
|
||||
},
|
||||
|
||||
start: function() {
|
||||
this.$el.addClass(this.fields_view.arch.attrs.class);
|
||||
return this._super();
|
||||
},
|
||||
|
||||
do_search: function(domain, context, group_by) {
|
||||
@@ -254,7 +252,7 @@ var KanbanView = View.extend({
|
||||
var is_empty = true;
|
||||
return $.when.apply(null, _.map(groups, function (group) {
|
||||
var def = $.when([]);
|
||||
var dataset = new data.DataSetSearch(self, self.dataset.model,
|
||||
var dataset = new data.DataSetSearch(self, self.model,
|
||||
new data.CompoundContext(self.dataset.get_context(), group.model.context()), group.model.domain());
|
||||
if (self.dataset._sort) {
|
||||
dataset.set_sort(self.dataset._sort);
|
||||
@@ -303,16 +301,11 @@ var KanbanView = View.extend({
|
||||
* $node may be undefined, in which case the ListView inserts them into this.options.$buttons
|
||||
*/
|
||||
render_buttons: function($node) {
|
||||
var display = false;
|
||||
if (this.options.action_buttons !== false) {
|
||||
display = this.is_action_enabled('create');
|
||||
} else if (!this.view_id && !this.options.read_only_mode) {
|
||||
display = this.is_action_enabled('write') || this.is_action_enabled('create');
|
||||
if (this.options.action_buttons !== false && this.is_action_enabled('create')) {
|
||||
this.$buttons = $(QWeb.render("KanbanView.buttons", {'widget': this}));
|
||||
this.$buttons.on('click', 'button.o-kanban-button-new', this.add_record.bind(this));
|
||||
this.$buttons.appendTo($node);
|
||||
}
|
||||
this.$buttons = $(QWeb.render("KanbanView.buttons", {'widget': this, display: display}));
|
||||
this.$buttons.on('click', 'button.o-kanban-button-new', this.add_record.bind(this));
|
||||
|
||||
this.$buttons.appendTo($node);
|
||||
},
|
||||
|
||||
render_pager: function($node, options) {
|
||||
@@ -499,7 +492,7 @@ var KanbanView = View.extend({
|
||||
context.set_eval_context({
|
||||
active_id: event.target.id,
|
||||
active_ids: [event.target.id],
|
||||
active_model: this.dataset.model,
|
||||
active_model: this.model,
|
||||
});
|
||||
this.do_execute_action(event.data, this.dataset, event.target.id).then(function () {
|
||||
self.reload_record(event.target);
|
||||
@@ -660,7 +653,7 @@ var KanbanView = View.extend({
|
||||
event.preventDefault();
|
||||
var popup = new form_common.SelectCreatePopup(this);
|
||||
popup.select_element(
|
||||
self.dataset.model,
|
||||
self.model,
|
||||
{
|
||||
title: _t("Create: "),
|
||||
initial_view: "form",
|
||||
@@ -688,7 +681,7 @@ var KanbanView = View.extend({
|
||||
model.call('create', [{name: event.data.value}], {
|
||||
context: this.search_context,
|
||||
}).then(function (id) {
|
||||
var dataset = new data.DataSetSearch(self, self.dataset.model, self.dataset.get_context(), []);
|
||||
var dataset = new data.DataSetSearch(self, self.model, self.dataset.get_context(), []);
|
||||
var group_data = {
|
||||
records: [],
|
||||
title: event.data.value,
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
<template>
|
||||
|
||||
<t t-name="KanbanView.buttons">
|
||||
<t t-if="display">
|
||||
<div>
|
||||
<button type="button" class="btn btn-primary btn-sm o-kanban-button-new" accesskey="C">
|
||||
<t t-esc="widget.options.create_text || _t('Create')"/>
|
||||
</button>
|
||||
</div>
|
||||
</t>
|
||||
<div>
|
||||
<button type="button" class="btn btn-primary btn-sm o-kanban-button-new" accesskey="C">
|
||||
<t t-esc="widget.options.create_text || _t('Create')"/>
|
||||
</button>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<t t-name="KanbanView.Group">
|
||||
|
||||
@@ -1532,6 +1532,38 @@ class BaseModel(object):
|
||||
|
||||
return view
|
||||
|
||||
def load_views(self, cr, uid, views, options=None, context=None):
|
||||
""" Returns the fields_views of given views, and optionally filters and fields.
|
||||
|
||||
:param views: list of [view_id, view_type]
|
||||
:param options.toolbar: true to include contextual actions when loading fields_views
|
||||
:param options.load_filters: true to return the model's filters
|
||||
:param options.action_id: id of the action to get the filters
|
||||
:param options.load_fields: true to load the model's fields
|
||||
:return: dictionary with fields_views, filters and fields
|
||||
"""
|
||||
if options is None:
|
||||
options = {}
|
||||
if context is None:
|
||||
context = {}
|
||||
result = {}
|
||||
|
||||
toolbar = options.get('toolbar')
|
||||
result['fields_views'] = {
|
||||
v_type: self.fields_view_get(cr, uid, v_id, v_type if v_type != 'list' else 'tree',
|
||||
toolbar=toolbar if v_type != 'search' else False,
|
||||
context=context)
|
||||
for [v_id, v_type] in views
|
||||
}
|
||||
|
||||
if options.get('load_filters'):
|
||||
result['filters'] = self.pool['ir.filters'].get_filters(cr, uid, self._name, options.get('action_id'), context=context)
|
||||
|
||||
if options.get('load_fields'):
|
||||
result['fields'] = self.fields_get(cr, uid, context=context)
|
||||
|
||||
return result
|
||||
|
||||
def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
|
||||
""" fields_view_get([view_id | view_type='form'])
|
||||
|
||||
|
||||
Reference in New Issue
Block a user