diff --git a/addons/base/__openerp__.py b/addons/base/__openerp__.py
index 9cf8a6c893e..cfe8850ab03 100644
--- a/addons/base/__openerp__.py
+++ b/addons/base/__openerp__.py
@@ -22,7 +22,7 @@
"static/lib/underscore/underscore.string.js",
"static/src/js/boot.js",
"static/src/js/core.js",
- "static/src/js/dates.js",
+ "static/src/js/formats.js",
"static/src/js/chrome.js",
"static/src/js/views.js",
"static/src/js/data.js",
diff --git a/addons/base/controllers/main.py b/addons/base/controllers/main.py
index aaf89853cad..4f6b3ea4f70 100644
--- a/addons/base/controllers/main.py
+++ b/addons/base/controllers/main.py
@@ -390,10 +390,10 @@ def load_actions_from_ir_values(req, key, key2, models, meta, context):
Values = req.session.model('ir.values')
actions = Values.get(key, key2, models, meta, context)
- return [(id, name, clean_action(action, req.session))
+ return [(id, name, clean_action(action, req.session, context=context))
for id, name, action in actions]
-def clean_action(action, session):
+def clean_action(action, session, context=None):
action.setdefault('flags', {})
if action['type'] != 'ir.actions.act_window':
return action
@@ -401,7 +401,7 @@ def clean_action(action, session):
if isinstance(action.get('context'), basestring):
action['context'] = eval(
action['context'],
- session.evaluation_context()) or {}
+ session.evaluation_context(context=context)) or {}
if isinstance(action.get('domain'), basestring):
action['domain'] = eval(
@@ -559,6 +559,7 @@ class DataSet(openerpweb.Controller):
:rtype: list
"""
Model = request.session.model(model)
+
context, domain = eval_context_and_domain(
request.session, request.context, domain)
@@ -581,9 +582,14 @@ class DataSet(openerpweb.Controller):
}
+ @openerpweb.jsonrequest
+ def read(self, request, model, ids, fields=False):
+ return self.do_search_read(request, model, ids, fields)
+
@openerpweb.jsonrequest
def get(self, request, model, ids, fields=False):
return self.do_get(request, model, ids, fields)
+
def do_get(self, request, model, ids, fields=False):
""" Fetches and returns the records of the model ``model`` whose ids
are in ``ids``.
@@ -668,6 +674,12 @@ class DataSet(openerpweb.Controller):
Model = req.session.model(model)
return Model.default_get(fields, req.session.eval_context(req.context))
+ @openerpweb.jsonrequest
+ def name_search(self, req, model, search_str, domain=[], context={}):
+ m = req.session.model(model)
+ r = m.name_search(search_str+'%', domain, '=ilike', context)
+ return {'result': r}
+
class DataGroup(openerpweb.Controller):
_cp_path = "/base/group"
@openerpweb.jsonrequest
@@ -1001,6 +1013,19 @@ class Action(openerpweb.Controller):
return clean_action(req.session.model('ir.actions.server').run(
[action_id], req.session.eval_context(req.context)), req.session)
+class TreeView(View):
+ _cp_path = "/base/treeview"
+
+ @openerpweb.jsonrequest
+ def load(self, req, model, view_id, toolbar=False):
+ return self.fields_view_get(req, model, view_id, 'tree', toolbar=toolbar)
+
+ @openerpweb.jsonrequest
+ def action(self, req, model, id):
+ return load_actions_from_ir_values(
+ req,'action', 'tree_but_open',[(model, id)],
+ False, req.session.eval_context(req.context))
+
def export_csv(fields, result):
fp = StringIO()
writer = csv.writer(fp, quoting=csv.QUOTE_ALL)
@@ -1213,4 +1238,3 @@ class Export(View):
return export_xls(field, result)
else:
return export_csv(field, result)
-
diff --git a/addons/base/static/lib/qweb/qweb2.js b/addons/base/static/lib/qweb/qweb2.js
index 7430464e2d8..89a77bf1b85 100644
--- a/addons/base/static/lib/qweb/qweb2.js
+++ b/addons/base/static/lib/qweb/qweb2.js
@@ -1,5 +1,7 @@
// TODO: trim support
// TODO: line number -> https://bugzilla.mozilla.org/show_bug.cgi?id=618650
+// TODO: templates orverwritten could be called by t-call="__super__" ?
+// TODO: t-set + t-value + children node == scoped variable ?
var QWeb2 = {
expressions_cache: {},
reserved_words: 'true,false,NaN,null,undefined,debugger,in,instanceof,new,function,return,this,typeof,eval,Math,RegExp,Array,Object,Date'.split(','),
@@ -21,6 +23,11 @@ var QWeb2 = {
}
throw new Error(prefix + ": " + message);
},
+ warning : function(message) {
+ if (typeof(window) !== 'undefined' && window.console) {
+ window.console.warn(message);
+ }
+ },
trim: function(s, mode) {
switch (mode) {
case "left":
@@ -638,6 +645,9 @@ QWeb2.Element = (function() {
compile_action_set : function(value) {
var variable = this.format_expression(value);
if (this.actions['value']) {
+ if (this.children.length) {
+ this.engine.tools.warning("@set with @value plus node chidren found. Children are ignored.");
+ }
this.top(variable + " = (" + (this.format_expression(this.actions['value'])) + ");");
this.process_children = false;
} else {
diff --git a/addons/base/static/src/css/base.css b/addons/base/static/src/css/base.css
index 7f670be071f..a629e559d4c 100644
--- a/addons/base/static/src/css/base.css
+++ b/addons/base/static/src/css/base.css
@@ -269,7 +269,7 @@ label.error {
border-radius: 4px;
-moz-border-radius: 4px;
-webkit-border-radius: 4px;
-
+
color: #eee;
text-shadow: #222 0 1px 0;
text-decoration: none;
@@ -926,8 +926,8 @@ label.error {
background-color: #D2D2FF;
}
.openerp td.invalid input, .openerp td.invalid select, .openerp td.invalid textarea {
- background-color: #F66;
- border: 1px solid #D00;
+ background-color: #F66 !important;
+ border: 1px solid #D00 !important;
}
.openerp div.oe-progressbar span {
position: absolute;
@@ -1053,7 +1053,7 @@ label.error {
padding-left: 10px;
padding-right: 21px;
height: 21px;
-
+
background: #ffffff; /* Old browsers */
background: -moz-linear-gradient(top, #ffffff 0%, #ebe9e9 100%); /* FF3.6+ */
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#ffffff), color-stop(100%,#ebe9e9)); /* Chrome,Safari4+ */
@@ -1062,7 +1062,7 @@ background: -o-linear-gradient(top, #ffffff 0%,#ebe9e9 100%); /* Opera11.10+ */
background: -ms-linear-gradient(top, #ffffff 0%,#ebe9e9 100%); /* IE10+ */
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#FFFFFF', endColorstr='#EBE9E9',GradientType=0 ); /* IE6-9 */
background: linear-gradient(top, #ffffff 0%,#ebe9e9 100%); /* W3C */
-
+
border: 1px solid #D2CFCF;
border-right-width: 0;
border-left-width: 0;
@@ -1167,7 +1167,9 @@ background: linear-gradient(top, #ffffff 0%,#ebe9e9 100%); /* W3C */
.openerp .oe-m2o-disabled-cm {
color: grey;
}
-
+.parent_top {
+ vertical-align: text-top;
+}
.openerp .oe-dialog-warning p {
padding-left: 1em;
font-size: 1.2em;
@@ -1180,3 +1182,30 @@ background: linear-gradient(top, #ffffff 0%,#ebe9e9 100%); /* W3C */
-webkit-box-shadow: none;
box-shadow: none;
}
+
+.openerp .oe-treeview-table {
+ width: 100%;
+}
+.treeview-tr, .treeview-td {
+ cursor: pointer;
+ vertical-align: top;
+ text-align: left;
+}
+.treeview-tr span, .treeview-td span {
+ display: block;
+}
+.treeview-tr:first-of-type {
+ background: transparent url(/base/static/src/img/expand.gif) 0 50% no-repeat;
+}
+.oe-open .treeview-tr:first-of-type {
+ background-image: url(/base/static/src/img/collapse.gif);
+}
+.treeview-tr:first-of-type span,
+.treeview-td:first-of-type span {
+ margin-left: 16px;
+}
+
+.treeview-header {
+ text-align: left;
+ vertical-align: top;
+}
diff --git a/addons/base/static/src/js/boot.js b/addons/base/static/src/js/boot.js
index 5754f5ccd14..7ac8a6bb1e0 100644
--- a/addons/base/static/src/js/boot.js
+++ b/addons/base/static/src/js/boot.js
@@ -60,7 +60,7 @@
openerp.base = function(instance) {
openerp.base.core(instance);
- openerp.base.dates(instance);
+ openerp.base.formats(instance);
openerp.base.chrome(instance);
openerp.base.data(instance);
if (openerp.base.views) {
diff --git a/addons/base/static/src/js/chrome.js b/addons/base/static/src/js/chrome.js
index 994ce846319..822dc52b5c2 100644
--- a/addons/base/static/src/js/chrome.js
+++ b/addons/base/static/src/js/chrome.js
@@ -949,9 +949,6 @@ openerp.base.Homepage = openerp.base.Widget.extend({
openerp.base.Preferences = openerp.base.Widget.extend({
});
-openerp.base.ImportExport = openerp.base.Widget.extend({
-});
-
openerp.base.WebClient = openerp.base.Widget.extend({
init: function(element_id) {
this._super(null, element_id);
@@ -981,7 +978,7 @@ openerp.base.WebClient = openerp.base.Widget.extend({
this.menu = new openerp.base.Menu(this, "oe_menu", "oe_secondary_menu");
this.menu.on_action.add(this.on_menu_action);
-
+
},
start: function() {
this.session.start();
@@ -991,8 +988,8 @@ openerp.base.WebClient = openerp.base.Widget.extend({
this.notification.notify("OpenERP Client", "The openerp client has been initialized.");
},
on_logged: function() {
- this.action_manager = new openerp.base.ActionManager(this, "oe_app");
- this.action_manager.start();
+ this.action_manager = new openerp.base.ActionManager(this);
+ this.action_manager.appendTo($("#oe_app"));
// if using saved actions, load the action and give it to action manager
var parameters = jQuery.deparam(jQuery.param.querystring());
@@ -1032,7 +1029,8 @@ openerp.base.WebClient = openerp.base.Widget.extend({
self.execute_home_action(home_action[0], ds);
})
},
- default_home: function () { },
+ default_home: function () {
+ },
/**
* Bundles the execution of the home action
*
diff --git a/addons/base/static/src/js/form.js b/addons/base/static/src/js/form.js
index 90e5e907c8b..e488bb3dfa1 100644
--- a/addons/base/static/src/js/form.js
+++ b/addons/base/static/src/js/form.js
@@ -1648,7 +1648,7 @@ openerp.base.form.FieldOne2Many = openerp.base.form.Field.extend({
});
this.views = views;
- this.viewmanager = new openerp.base.ViewManager(this, this.element_id, this.dataset, views);
+ this.viewmanager = new openerp.base.ViewManager(this, this.dataset, views);
this.viewmanager.registry = openerp.base.views.clone({
list: 'openerp.base.form.One2ManyListView',
form: 'openerp.base.form.One2ManyFormView'
@@ -1674,7 +1674,7 @@ openerp.base.form.FieldOne2Many = openerp.base.form.Field.extend({
self.save_form_view();
});
setTimeout(function () {
- self.viewmanager.start();
+ self.viewmanager.appendTo(this.$element);
}, 0);
},
reload_current_view: function() {
diff --git a/addons/base/static/src/js/dates.js b/addons/base/static/src/js/formats.js
similarity index 56%
rename from addons/base/static/src/js/dates.js
rename to addons/base/static/src/js/formats.js
index 8daea39773f..3157997e82d 100644
--- a/addons/base/static/src/js/dates.js
+++ b/addons/base/static/src/js/formats.js
@@ -1,5 +1,5 @@
-openerp.base.dates = function(openerp) {
+openerp.base.formats = function(openerp) {
/**
* Converts a string to a Date javascript object using OpenERP's
@@ -133,5 +133,75 @@ openerp.base.format_time = function(obj) {
return fts(obj.getHours(),2) + ":" + fts(obj.getMinutes(),2) + ":"
+ fts(obj.getSeconds(),2);
};
+
+/**
+ * Formats a single atomic value based on a field descriptor
+ *
+ * @param {Object} value read from OpenERP
+ * @param {Object} descriptor union of orm field and view field
+ * @param {Object} [descriptor.widget] widget to use to display the value
+ * @param {Object} descriptor.type fallback if no widget is provided, or if the provided widget is unknown
+ * @param {Object} [descriptor.digits] used for the formatting of floats
+ * @param {String} [value_if_empty=''] returned if the ``value`` argument is considered empty
+ */
+openerp.base.format_value = function (value, descriptor, value_if_empty) {
+ // If NaN value, display as with a `false` (empty cell)
+ if (typeof value === 'number' && isNaN(value)) {
+ value = false;
+ }
+ switch (value) {
+ case false:
+ case Infinity:
+ case -Infinity:
+ return value_if_empty === undefined ? '' : value_if_empty;
+ }
+ switch (descriptor.widget || descriptor.type) {
+ case 'integer':
+ return _.sprintf('%d', value);
+ case 'float':
+ var precision = descriptor.digits ? descriptor.digits[1] : 2;
+ return _.sprintf('%.' + precision + 'f', value);
+ case 'float_time':
+ return _.sprintf("%02d:%02d",
+ Math.floor(value),
+ Math.round((value % 1) * 60));
+ case 'progressbar':
+ return _.sprintf(
+ '',
+ value, value);
+ case 'many2one':
+ // name_get value format
+ return value[1];
+ default:
+ return value;
+ }
+};
+
+/**
+ * Formats a provided cell based on its field type
+ *
+ * @param {Object} row_data record whose values should be displayed in the cell
+ * @param {Object} column column descriptor
+ * @param {"button"|"field"} column.tag base control type
+ * @param {String} column.type widget type for a field control
+ * @param {String} [column.string] button label
+ * @param {String} [column.icon] button icon
+ * @param {String} [value_if_empty=''] what to display if the field's value is ``false``
+ */
+openerp.base.format_cell = function (row_data, column, value_if_empty) {
+ var attrs = column.modifiers_for(row_data);
+ if (attrs.invisible) { return ''; }
+ if (column.tag === 'button') {
+ return [
+ ''
+ ].join('')
+ }
+
+ return openerp.base.format_value(
+ row_data[column.id].value, column, value_if_empty);
+}
};
diff --git a/addons/base/static/src/js/list.js b/addons/base/static/src/js/list.js
index ea9cdaea1ee..11440a003b5 100644
--- a/addons/base/static/src/js/list.js
+++ b/addons/base/static/src/js/list.js
@@ -1,63 +1,5 @@
openerp.base.list = function (openerp) {
openerp.base.views.add('list', 'openerp.base.ListView');
-openerp.base.list = {
- /**
- * Formats the rendring of a given value based on its field type
- *
- * @param {Object} row_data record whose values should be displayed in the cell
- * @param {Object} column column descriptor
- * @param {"button"|"field"} column.tag base control type
- * @param {String} column.type widget type for a field control
- * @param {String} [column.string] button label
- * @param {String} [column.icon] button icon
- * @param {String} [value_if_empty=''] what to display if the field's value is ``false``
- */
- render_cell: function (row_data, column, value_if_empty) {
- var attrs = column.modifiers_for(row_data);
- if (attrs.invisible) { return ''; }
- if (column.tag === 'button') {
- return [
- ''
- ].join('')
- }
-
- var value = row_data[column.id].value;
-
- // If NaN value, display as with a `false` (empty cell)
- if (typeof value === 'number' && isNaN(value)) {
- value = false;
- }
- switch (value) {
- case false:
- case Infinity:
- case -Infinity:
- return value_if_empty === undefined ? '' : value_if_empty;
- }
- switch (column.widget || column.type) {
- case 'integer':
- return _.sprintf('%d', value);
- case 'float':
- var precision = column.digits ? column.digits[1] : 2;
- return _.sprintf('%.' + precision + 'f', value);
- case 'float_time':
- return _.sprintf("%02d:%02d",
- Math.floor(value),
- Math.round((value % 1) * 60));
- case 'progressbar':
- return _.sprintf(
- '',
- value, value);
- case 'many2one':
- // name_get value format
- return value[1];
- default:
- return value;
- }
- }
-};
openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListView# */ {
defaults: {
// records can be selected one by one
@@ -637,7 +579,7 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi
}
$footer_cells.filter(_.sprintf('[data-field=%s]', column.id))
- .html(openerp.base.list.render_cell(aggregation, column));
+ .html(openerp.base.format_cell(aggregation, column));
});
}
// TODO: implement reorder (drag and drop rows)
@@ -725,7 +667,7 @@ openerp.base.ListView.List = openerp.base.Class.extend( /** @lends openerp.base.
this.$current = this.$_element.clone(true);
this.$current.empty().append(
QWeb.render('ListView.rows', _.extend({
- render_cell: openerp.base.list.render_cell}, this)));
+ render_cell: openerp.base.format_cell}, this)));
},
/**
* Gets the ids of all currently selected records, if any
@@ -856,7 +798,7 @@ openerp.base.ListView.List = openerp.base.Class.extend( /** @lends openerp.base.
row: this.rows[record_index],
row_parity: (record_index % 2 === 0) ? 'even' : 'odd',
row_index: record_index,
- render_cell: openerp.base.list.render_cell
+ render_cell: openerp.base.format_cell
});
},
/**
@@ -1016,7 +958,7 @@ openerp.base.ListView.Groups = openerp.base.Class.extend( /** @lends openerp.bas
row_data[group.grouped_on] = group;
var group_column = _(self.columns).detect(function (column) {
return column.id === group.grouped_on; });
- $group_column.html(openerp.base.list.render_cell(
+ $group_column.html(openerp.base.format_cell(
row_data, group_column, "Undefined"
));
if (group.openable) {
diff --git a/addons/base/static/src/js/view_tree.js b/addons/base/static/src/js/view_tree.js
index 90972f84973..05f58c077ff 100644
--- a/addons/base/static/src/js/view_tree.js
+++ b/addons/base/static/src/js/view_tree.js
@@ -5,22 +5,203 @@
openerp.base.view_tree = function(openerp) {
openerp.base.views.add('tree', 'openerp.base.TreeView');
-openerp.base.TreeView = openerp.base.Widget.extend({
/**
* Genuine tree view (the one displayed as a tree, not the list)
*/
+openerp.base.TreeView = openerp.base.View.extend({
+ /**
+ * Indicates that this view is not searchable, and thus that no search
+ * view should be displayed (if there is one active).
+ */
+ searchable : false,
+
+ init: function(parent, element_id, dataset, view_id, options) {
+ this._super(parent, element_id);
+ this.dataset = dataset;
+ this.model = dataset.model;
+ this.view_id = view_id;
+
+ this.records = {};
+
+ this.options = _.extend({}, this.defaults, options || {});
+ },
+
start: function () {
this._super();
- this.$element.append('Tree view');
+ return this.rpc("/base/treeview/load", {
+ model: this.model,
+ view_id: this.view_id,
+ toolbar: this.view_manager ? !!this.view_manager.sidebar : false
+ }, this.on_loaded);
},
+ /**
+ * Returns the list of fields needed to correctly read objects.
+ *
+ * Gathers the names of all fields in fields_view_get, and adds the
+ * field_parent (children_field in the tree view) if it's not already one
+ * of the fields to fetch
+ *
+ * @returns {Array} an array of fields which can be provided to DataSet.read_slice and others
+ */
+ fields_list: function () {
+ var fields = _.keys(this.fields);
+ if (!_(fields).contains(this.children_field)) {
+ fields.push(this.children_field);
+ }
+ return fields;
+ },
+ on_loaded: 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;
+ _(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;
+ this.hook_row_click();
+ this.$element.html(QWeb.render('TreeView', {
+ 'title': this.fields_view.arch.attrs.string,
+ 'fields_view': this.fields_view.arch.children,
+ 'fields': this.fields,
+ 'toolbar': has_toolbar
+ }));
+
+ this.dataset.read_slice(this.fields_list(), 0, false, function (records) {
+ if (!has_toolbar) {
+ // WARNING: will do a second read on the same ids, but only on
+ // first load so not very important
+ self.getdata(null, _(records).pluck('id'));
+ return;
+ }
+
+ var $select = self.$element.find('select')
+ .change(function () {
+ var $option = $(this).find(':selected');
+ self.getdata($option.val(), $option.data('children'));
+ });
+ _(records).each(function (record) {
+ self.records[record.id] = record;
+ $('