diff --git a/addons/base/controllers/main.py b/addons/base/controllers/main.py index 9ce3c0f58e8..0a5b3ba64b0 100644 --- a/addons/base/controllers/main.py +++ b/addons/base/controllers/main.py @@ -439,9 +439,12 @@ class DataSet(openerpweb.Controller): class DataGroup(openerpweb.Controller): _cp_path = "/base/group" @openerpweb.jsonrequest - def read(self, request, model, group_by_fields, domain=None, context=None): + def read(self, request, model, group_by_fields, domain=None): Model = request.session.model(model) - return Model.read_group(domain or False, False, group_by_fields, 0, False, context or False) + + return Model.read_group( + domain or [], False, group_by_fields, 0, False, + dict(request.context, group_by=group_by_fields)) class View(openerpweb.Controller): def fields_view_get(self, request, model, view_id, view_type, @@ -555,6 +558,113 @@ class FormView(View): fields_view = self.fields_view_get(req, model, view_id, 'form', toolbar=toolbar) return {'fields_view': fields_view} +class ListView(View): + _cp_path = "/base/listview" + + @openerpweb.jsonrequest + def load(self, req, model, view_id, toolbar=False): + fields_view = self.fields_view_get(req, model, view_id, 'tree', toolbar=toolbar) + return {'fields_view': fields_view} + + def fields_view_get(self, request, model, view_id, view_type="tree", + transform=True, toolbar=False, submenu=False): + """ Sets @editable on the view's arch if it isn't already set and + ``set_editable`` is present in the request context + """ + view = super(ListView, self).fields_view_get( + request, model, view_id, view_type, transform, toolbar, submenu) + + view_attributes = view['arch']['attrs'] + if request.context.get('set_editable')\ + and 'editable' not in view_attributes: + view_attributes['editable'] = 'bottom' + return view + + @openerpweb.jsonrequest + def fill(self, request, model, id, domain, + offset=0, limit=False, sort=None): + return self.do_fill(request, model, id, domain, offset, limit, sort) + + def do_fill(self, request, model, id, domain, + offset=0, limit=False, sort=None): + """ Returns all information needed to fill a table: + + * view with processed ``editable`` flag + * fields (columns) with processed ``invisible`` flag + * rows with processed ``attrs`` and ``colors`` + + .. note:: context is passed through ``request`` parameter + + :param request: OpenERP request + :type request: openerpweb.openerpweb.JsonRequest + :type str model: OpenERP model for this list view + :type int id: view_id, or False if none provided + :param list domain: the search domain to search for + :param int offset: search offset, for pagination + :param int limit: search limit, for pagination + :returns: hell if I have any idea yet + """ + view = self.fields_view_get(request, model, id, toolbar=True) + + rows = DataSet().do_search_read(request, model, + offset=offset, limit=limit, + domain=domain, sort=sort) + eval_context = request.session.evaluation_context( + request.context) + + if sort: + sort_criteria = sort.split(',')[0].split(' ') + view['sorted'] = { + 'field': sort_criteria[0], + 'reversed': sort_criteria[1] == 'DESC' + } + else: + view['sorted'] = {} + return { + 'view': view, + 'records': [ + {'data': dict((key, {'value': value}) + for key, value in row.iteritems()), + 'color': self.process_colors(view, row, eval_context)} + for row in rows + ] + } + + def process_colors(self, view, row, context): + colors = view['arch']['attrs'].get('colors') + + if not colors: + return None + + color = [ + pair.split(':')[0] + for pair in colors.split(';') + if eval(pair.split(':')[1], dict(context, **row)) + ] + + if not color: + return None + elif len(color) == 1: + return color[0] + return 'maroon' + +class SearchView(View): + _cp_path = "/base/searchview" + + @openerpweb.jsonrequest + def load(self, req, model, view_id): + fields_view = self.fields_view_get(req, model, view_id, 'search') + return {'fields_view': fields_view} + + @openerpweb.jsonrequest + def fields_get(self, req, model): + Model = req.session.model(model) + fields = Model.fields_get() + return {'fields': fields} + +class Binary(openerpweb.Controller): + _cp_path = "/base/binary" + @openerpweb.httprequest def image(self, request, session_id, model, id, field, **kw): cherrypy.response.headers['Content-Type'] = 'image/png' @@ -612,113 +722,6 @@ class FormView(View): args = [False, e.message] return out % (simplejson.dumps(callback), simplejson.dumps(args)) -class ListView(View): - _cp_path = "/base/listview" - - @openerpweb.jsonrequest - def load(self, req, model, view_id, toolbar=False): - fields_view = self.fields_view_get(req, model, view_id, 'tree', toolbar=toolbar) - return {'fields_view': fields_view} - - def fields_view_get(self, request, model, view_id, view_type="tree", - transform=True, toolbar=False, submenu=False): - """ Sets @editable on the view's arch if it isn't already set and - ``set_editable`` is present in the request context - """ - view = super(ListView, self).fields_view_get( - request, model, view_id, view_type, transform, toolbar, submenu) - - view_attributes = view['arch']['attrs'] - if request.context.get('set_editable')\ - and 'editable' not in view_attributes: - view_attributes['editable'] = 'bottom' - return view - - @openerpweb.jsonrequest - def fill(self, request, model, id, domain, - offset=0, limit=False, sort=None): - return self.do_fill(request, model, id, domain, offset, limit, sort) - - def do_fill(self, request, model, id, domain, - offset=0, limit=False, sort=None): - """ Returns all information needed to fill a table: - - * view with processed ``editable`` flag - * fields (columns) with processed ``invisible`` flag - * rows with processed ``attrs`` and ``colors`` - - .. note:: context is passed through ``request`` parameter - - :param request: OpenERP request - :type request: openerpweb.openerpweb.JsonRequest - :type str model: OpenERP model for this list view - :type int id: view_id, or False if none provided - :param list domain: the search domain to search for - :param int offset: search offset, for pagination - :param int limit: search limit, for pagination - :returns: hell if I have any idea yet - """ - view = self.fields_view_get(request, model, id, toolbar=True) - - print sort - rows = DataSet().do_search_read(request, model, - offset=offset, limit=limit, - domain=domain, sort=sort) - eval_context = request.session.evaluation_context( - request.context) - - if sort: - sort_criteria = sort.split(',')[0].split(' ') - print sort, sort_criteria - view['sorted'] = { - 'field': sort_criteria[0], - 'reversed': sort_criteria[1] == 'DESC' - } - else: - view['sorted'] = {} - return { - 'view': view, - 'records': [ - {'data': dict((key, {'value': value}) - for key, value in row.iteritems()), - 'color': self.process_colors(view, row, eval_context)} - for row in rows - ] - } - - def process_colors(self, view, row, context): - colors = view['arch']['attrs'].get('colors') - - if not colors: - return None - - color = [ - pair.split(':')[0] - for pair in colors.split(';') - if eval(pair.split(':')[1], dict(context, **row)) - ] - - if not color: - return None - elif len(color) == 1: - return color[0] - return 'maroon' - - -class SearchView(View): - _cp_path = "/base/searchview" - - @openerpweb.jsonrequest - def load(self, req, model, view_id): - fields_view = self.fields_view_get(req, model, view_id, 'search') - return {'fields_view': fields_view} - - @openerpweb.jsonrequest - def fields_get(self, req, model): - Model = req.session.model(model) - fields = Model.fields_get() - return {'fields': fields} - class Action(openerpweb.Controller): _cp_path = "/base/action" diff --git a/addons/base/static/lib/underscore/underscore.string.js b/addons/base/static/lib/underscore/underscore.string.js new file mode 100644 index 00000000000..4da2b26ec1c --- /dev/null +++ b/addons/base/static/lib/underscore/underscore.string.js @@ -0,0 +1,304 @@ +// Underscore.string +// (c) 2010 Esa-Matti Suuronen +// Underscore.strings is freely distributable under the terms of the MIT license. +// Documentation: https://github.com/edtsech/underscore.string +// Some code is borrowed from MooTools and Alexandru Marasteanu. + +// Version 1.1.4 + +(function(){ + // ------------------------- Baseline setup --------------------------------- + + // Establish the root object, "window" in the browser, or "global" on the server. + var root = this; + + var nativeTrim = String.prototype.trim; + + function str_repeat(i, m) { + for (var o = []; m > 0; o[--m] = i); + return o.join(''); + } + + function defaultToWhiteSpace(characters){ + if (characters) { + return _s.escapeRegExp(characters); + } + return '\\s'; + } + + var _s = { + + isBlank: function(str){ + return !!str.match(/^\s*$/); + }, + + capitalize : function(str) { + return str.charAt(0).toUpperCase() + str.substring(1).toLowerCase(); + }, + + chop: function(str, step){ + step = step || str.length; + var arr = []; + for (var i = 0; i < str.length;) { + arr.push(str.slice(i,i + step)); + i = i + step; + } + return arr; + }, + + clean: function(str){ + return _s.strip(str.replace(/\s+/g, ' ')); + }, + + count: function(str, substr){ + var count = 0, index; + for (var i=0; i < str.length;) { + index = str.indexOf(substr, i); + index >= 0 && count++; + i = i + (index >= 0 ? index : 0) + substr.length; + } + return count; + }, + + chars: function(str) { + return str.split(''); + }, + + escapeHTML: function(str) { + return String(str||'').replace(/&/g,'&').replace(//g,'>') + .replace(/"/g, '"').replace(/'/g, "'"); + }, + + unescapeHTML: function(str) { + return String(str||'').replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>') + .replace(/"/g, '"').replace(/'/g, "'"); + }, + + escapeRegExp: function(str){ + // From MooTools core 1.2.4 + return String(str||'').replace(/([-.*+?^${}()|[\]\/\\])/g, '\\$1'); + }, + + insert: function(str, i, substr){ + var arr = str.split(''); + arr.splice(i, 0, substr); + return arr.join(''); + }, + + includes: function(str, needle){ + return str.indexOf(needle) !== -1; + }, + + join: function(sep) { + // TODO: Could this be faster by converting + // arguments to Array and using array.join(sep)? + sep = String(sep); + var str = ""; + for (var i=1; i < arguments.length; i += 1) { + str += String(arguments[i]); + if ( i !== arguments.length-1 ) { + str += sep; + } + } + return str; + }, + + lines: function(str) { + return str.split("\n"); + }, + +// reverse: function(str){ +// return Array.prototype.reverse.apply(str.split('')).join(''); +// }, + + splice: function(str, i, howmany, substr){ + var arr = str.split(''); + arr.splice(i, howmany, substr); + return arr.join(''); + }, + + startsWith: function(str, starts){ + return str.length >= starts.length && str.substring(0, starts.length) === starts; + }, + + endsWith: function(str, ends){ + return str.length >= ends.length && str.substring(str.length - ends.length) === ends; + }, + + succ: function(str){ + var arr = str.split(''); + arr.splice(str.length-1, 1, String.fromCharCode(str.charCodeAt(str.length-1) + 1)); + return arr.join(''); + }, + + titleize: function(str){ + var arr = str.split(' '), + word; + for (var i=0; i < arr.length; i++) { + word = arr[i].split(''); + if(typeof word[0] !== 'undefined') word[0] = word[0].toUpperCase(); + i+1 === arr.length ? arr[i] = word.join('') : arr[i] = word.join('') + ' '; + } + return arr.join(''); + }, + + camelize: function(str){ + return _s.trim(str).replace(/(\-|_|\s)+(.)?/g, function(match, separator, chr) { + return chr ? chr.toUpperCase() : ''; + }); + }, + + underscored: function(str){ + return _s.trim(str).replace(/([a-z\d])([A-Z]+)/g, '$1_$2').replace(/\-|\s+/g, '_').toLowerCase(); + }, + + dasherize: function(str){ + return _s.trim(str).replace(/([a-z\d])([A-Z]+)/g, '$1-$2').replace(/^([A-Z]+)/, '-$1').replace(/\_|\s+/g, '-').toLowerCase(); + }, + + trim: function(str, characters){ + if (!characters && nativeTrim) { + return nativeTrim.call(str); + } + characters = defaultToWhiteSpace(characters); + return str.replace(new RegExp('\^[' + characters + ']+|[' + characters + ']+$', 'g'), ''); + }, + + ltrim: function(str, characters){ + characters = defaultToWhiteSpace(characters); + return str.replace(new RegExp('\^[' + characters + ']+', 'g'), ''); + }, + + rtrim: function(str, characters){ + characters = defaultToWhiteSpace(characters); + return str.replace(new RegExp('[' + characters + ']+$', 'g'), ''); + }, + + truncate: function(str, length, truncateStr){ + truncateStr = truncateStr || '...'; + return str.slice(0,length) + truncateStr; + }, + + words: function(str, delimiter) { + delimiter = delimiter || " "; + return str.split(delimiter); + }, + + + pad: function(str, length, padStr, type) { + + var padding = ''; + var padlen = 0; + + if (!padStr) { padStr = ' '; } + else if (padStr.length > 1) { padStr = padStr[0]; } + switch(type) { + case "right": + padlen = (length - str.length); + padding = str_repeat(padStr, padlen); + str = str+padding; + break; + case "both": + padlen = (length - str.length); + padding = { + 'left' : str_repeat(padStr, Math.ceil(padlen/2)), + 'right': str_repeat(padStr, Math.floor(padlen/2)) + }; + str = padding.left+str+padding.right; + break; + default: // "left" + padlen = (length - str.length); + padding = str_repeat(padStr, padlen);; + str = padding+str; + } + return str; + }, + + lpad: function(str, length, padStr) { + return _s.pad(str, length, padStr); + }, + + rpad: function(str, length, padStr) { + return _s.pad(str, length, padStr, 'right'); + }, + + lrpad: function(str, length, padStr) { + return _s.pad(str, length, padStr, 'both'); + }, + + + /** + * Credits for this function goes to + * http://www.diveintojavascript.com/projects/sprintf-for-javascript + * + * Copyright (c) Alexandru Marasteanu + * All rights reserved. + * */ + sprintf: function(){ + + var i = 0, a, f = arguments[i++], o = [], m, p, c, x, s = ''; + while (f) { + if (m = /^[^\x25]+/.exec(f)) { + o.push(m[0]); + } + else if (m = /^\x25{2}/.exec(f)) { + o.push('%'); + } + else if (m = /^\x25(?:(\d+)\$)?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(f)) { + if (((a = arguments[m[1] || i++]) == null) || (a == undefined)) { + throw('Too few arguments.'); + } + if (/[^s]/.test(m[7]) && (typeof(a) != 'number')) { + throw('Expecting number but found ' + typeof(a)); + } + switch (m[7]) { + case 'b': a = a.toString(2); break; + case 'c': a = String.fromCharCode(a); break; + case 'd': a = parseInt(a); break; + case 'e': a = m[6] ? a.toExponential(m[6]) : a.toExponential(); break; + case 'f': a = m[6] ? parseFloat(a).toFixed(m[6]) : parseFloat(a); break; + case 'o': a = a.toString(8); break; + case 's': a = ((a = String(a)) && m[6] ? a.substring(0, m[6]) : a); break; + case 'u': a = Math.abs(a); break; + case 'x': a = a.toString(16); break; + case 'X': a = a.toString(16).toUpperCase(); break; + } + a = (/[def]/.test(m[7]) && m[2] && a >= 0 ? '+'+ a : a); + c = m[3] ? m[3] == '0' ? '0' : m[3].charAt(1) : ' '; + x = m[5] - String(a).length - s.length; + p = m[5] ? str_repeat(c, x) : ''; + o.push(s + (m[4] ? a + p : p + a)); + } + else { + throw('Huh ?!'); + } + f = f.substring(m[0].length); + } + return o.join(''); + } + } + + // Aliases + + _s.strip = _s.trim; + _s.lstrip = _s.ltrim; + _s.rstrip = _s.rtrim; + _s.center = _s.lrpad + _s.ljust = _s.lpad + _s.rjust = _s.rpad + + // CommonJS module is defined + if (typeof window === 'undefined' && typeof module !== 'undefined') { + // Export module + module.exports = _s; + + // Integrate with Underscore.js + } else if (typeof root._ !== 'undefined') { + root._.mixin(_s); + + // Or define it + } else { + root._ = _s; + } + +}()); diff --git a/addons/base/static/lib/underscore/underscore.strings.js b/addons/base/static/lib/underscore/underscore.strings.js deleted file mode 100644 index 6e66014e3e9..00000000000 --- a/addons/base/static/lib/underscore/underscore.strings.js +++ /dev/null @@ -1,161 +0,0 @@ -// Underscore.strings -// (c) 2010 Esa-Matti Suuronen -// Underscore.strings is freely distributable under the terms of the MIT license. -// Documentation: http://bitbucket.org/epeli/underscore.strings/ -// Some code is borrowed from MooTools and Alexandru Marasteanu. - -// Version 1.0 - - -(function(){ - // ------------------------- Baseline setup --------------------------------- - - // Establish the root object, "window" in the browser, or "global" on the server. - var root = this; - - var nativeTrim = String.prototype.trim; - - function str_repeat(i, m) { - for (var o = []; m > 0; o[--m] = i); - return o.join(''); - } - - function defaultToWhiteSpace(characters){ - if (characters) { - return _s.escapeRegExp(characters); - } - return '\\s'; - } - - var _s; - _s = root._s = { - - capitalize : function(str) { - return str.charAt(0).toUpperCase() + str.substring(1).toLowerCase(); - }, - - join: function(sep) { - // TODO: Could this be faster by converting - // arguments to Array and using array.join(sep)? - sep = String(sep); - var str = ""; - for (var i=1; i < arguments.length; i += 1) { - str += String(arguments[i]); - if ( i !== arguments.length-1 ) { - str += sep; - } - } - return str; - }, - - escapeRegExp: function(str){ - // From MooTools core 1.2.4 - return str.replace(/([-.*+?^${}()|[\]\/\\])/g, '\\$1'); - }, - - reverse: function(str){ - return Array.prototype.reverse.apply(str.split('')).join(''); - }, - - contains: function(str, needle){ - return str.indexOf(needle) !== -1; - }, - - clean: function(str){ - return _s.strip(str.replace(/\s+/g, ' ')); - }, - - trim: function(str, characters){ - if (!characters && nativeTrim) { - return nativeTrim.call(str); - } - characters = defaultToWhiteSpace(characters); - return str.replace(new RegExp('\^[' + characters + ']+|[' + characters + ']+$', 'g'), ''); - }, - - - - ltrim: function(str, characters){ - characters = defaultToWhiteSpace(characters); - return str.replace(new RegExp('\^[' + characters + ']+', 'g'), ''); - }, - - rtrim: function(str, characters){ - characters = defaultToWhiteSpace(characters); - return str.replace(new RegExp('[' + characters + ']+$', 'g'), ''); - }, - - - startsWith: function(str, starts){ - return str.length >= starts.length && str.substring(0, starts.length) === starts; - }, - - - endsWith: function(str, ends){ - return str.length >= ends.length && str.substring(str.length - ends.length) === ends; - }, - - - /** - * Credits for this function goes to - * http://www.diveintojavascript.com/projects/sprintf-for-javascript - * - * Copyright (c) Alexandru Marasteanu - * All rights reserved. - * */ - sprintf: function(){ - - var i = 0, a, f = arguments[i++], o = [], m, p, c, x, s = ''; - while (f) { - if (m = /^[^\x25]+/.exec(f)) { - o.push(m[0]); - } - else if (m = /^\x25{2}/.exec(f)) { - o.push('%'); - } - else if (m = /^\x25(?:(\d+)\$)?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(f)) { - if (((a = arguments[m[1] || i++]) == null) || (a == undefined)) { - throw('Too few arguments.'); - } - if (/[^s]/.test(m[7]) && (typeof(a) != 'number')) { - throw('Expecting number but found ' + typeof(a)); - } - switch (m[7]) { - case 'b': a = a.toString(2); break; - case 'c': a = String.fromCharCode(a); break; - case 'd': a = parseInt(a); break; - case 'e': a = m[6] ? a.toExponential(m[6]) : a.toExponential(); break; - case 'f': a = m[6] ? parseFloat(a).toFixed(m[6]) : parseFloat(a); break; - case 'o': a = a.toString(8); break; - case 's': a = ((a = String(a)) && m[6] ? a.substring(0, m[6]) : a); break; - case 'u': a = Math.abs(a); break; - case 'x': a = a.toString(16); break; - case 'X': a = a.toString(16).toUpperCase(); break; - } - a = (/[def]/.test(m[7]) && m[2] && a >= 0 ? '+'+ a : a); - c = m[3] ? m[3] == '0' ? '0' : m[3].charAt(1) : ' '; - x = m[5] - String(a).length - s.length; - p = m[5] ? str_repeat(c, x) : ''; - o.push(s + (m[4] ? a + p : p + a)); - } - else { - throw('Huh ?!'); - } - f = f.substring(m[0].length); - } - return o.join(''); - } - } - - // Some aliases - root._s.strip = _s.trim; - root._s.lstrip = _s.ltrim; - root._s.rstrip = _s.rtrim; - - - // Integrate with Underscore.js - if (root._) { - root._.mixin(root._s); - } - -}()); diff --git a/addons/base/static/src/base.html b/addons/base/static/src/base.html index 314422d826b..467bccb92ff 100644 --- a/addons/base/static/src/base.html +++ b/addons/base/static/src/base.html @@ -7,7 +7,7 @@ - + - + @@ -416,22 +426,28 @@
- - - - + + + + + +
+
+
+ + + + +
+
+
+ +