diff --git a/addons/web/controllers/main.py b/addons/web/controllers/main.py index f2641cf6136..40c949483fa 100644 --- a/addons/web/controllers/main.py +++ b/addons/web/controllers/main.py @@ -1052,7 +1052,10 @@ class DataSet(http.Controller): } records = Model.read(ids, fields or False, request.context) - records.sort(key=lambda obj: ids.index(obj['id'])) + + index = dict((r['id'], r) for r in records) + records = [index[x] for x in ids if x in index] + return { 'length': length, 'records': records @@ -1529,7 +1532,7 @@ class Export(http.Controller): fields[base]['relation'], base, fields[base]['string'], subfields )) - else: + elif base in fields: info[base] = fields[base]['string'] return info diff --git a/addons/web/static/lib/underscore/underscore.js b/addons/web/static/lib/underscore/underscore.js index b50115df5c1..ca61fdc3a4b 100644 --- a/addons/web/static/lib/underscore/underscore.js +++ b/addons/web/static/lib/underscore/underscore.js @@ -1,6 +1,6 @@ -// Underscore.js 1.5.2 +// Underscore.js 1.6.0 // http://underscorejs.org -// (c) 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors +// (c) 2009-2014 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors // Underscore may be freely distributed under the MIT license. (function() { @@ -65,7 +65,7 @@ } // Current version. - _.VERSION = '1.5.2'; + _.VERSION = '1.6.0'; // Collection Functions // -------------------- @@ -74,7 +74,7 @@ // Handles objects with the built-in `forEach`, arrays, and raw objects. // Delegates to **ECMAScript 5**'s native `forEach` if available. var each = _.each = _.forEach = function(obj, iterator, context) { - if (obj == null) return; + if (obj == null) return obj; if (nativeForEach && obj.forEach === nativeForEach) { obj.forEach(iterator, context); } else if (obj.length === +obj.length) { @@ -87,6 +87,7 @@ if (iterator.call(context, obj[keys[i]], keys[i], obj) === breaker) return; } } + return obj; }; // Return the results of applying the iterator to each element. @@ -152,10 +153,10 @@ }; // Return the first value which passes a truth test. Aliased as `detect`. - _.find = _.detect = function(obj, iterator, context) { + _.find = _.detect = function(obj, predicate, context) { var result; any(obj, function(value, index, list) { - if (iterator.call(context, value, index, list)) { + if (predicate.call(context, value, index, list)) { result = value; return true; } @@ -166,33 +167,33 @@ // Return all the elements that pass a truth test. // Delegates to **ECMAScript 5**'s native `filter` if available. // Aliased as `select`. - _.filter = _.select = function(obj, iterator, context) { + _.filter = _.select = function(obj, predicate, context) { var results = []; if (obj == null) return results; - if (nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context); + if (nativeFilter && obj.filter === nativeFilter) return obj.filter(predicate, context); each(obj, function(value, index, list) { - if (iterator.call(context, value, index, list)) results.push(value); + if (predicate.call(context, value, index, list)) results.push(value); }); return results; }; // Return all the elements for which a truth test fails. - _.reject = function(obj, iterator, context) { + _.reject = function(obj, predicate, context) { return _.filter(obj, function(value, index, list) { - return !iterator.call(context, value, index, list); + return !predicate.call(context, value, index, list); }, context); }; // Determine whether all of the elements match a truth test. // Delegates to **ECMAScript 5**'s native `every` if available. // Aliased as `all`. - _.every = _.all = function(obj, iterator, context) { - iterator || (iterator = _.identity); + _.every = _.all = function(obj, predicate, context) { + predicate || (predicate = _.identity); var result = true; if (obj == null) return result; - if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context); + if (nativeEvery && obj.every === nativeEvery) return obj.every(predicate, context); each(obj, function(value, index, list) { - if (!(result = result && iterator.call(context, value, index, list))) return breaker; + if (!(result = result && predicate.call(context, value, index, list))) return breaker; }); return !!result; }; @@ -200,13 +201,13 @@ // Determine if at least one element in the object matches a truth test. // Delegates to **ECMAScript 5**'s native `some` if available. // Aliased as `any`. - var any = _.some = _.any = function(obj, iterator, context) { - iterator || (iterator = _.identity); + var any = _.some = _.any = function(obj, predicate, context) { + predicate || (predicate = _.identity); var result = false; if (obj == null) return result; - if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context); + if (nativeSome && obj.some === nativeSome) return obj.some(predicate, context); each(obj, function(value, index, list) { - if (result || (result = iterator.call(context, value, index, list))) return breaker; + if (result || (result = predicate.call(context, value, index, list))) return breaker; }); return !!result; }; @@ -232,25 +233,19 @@ // Convenience version of a common use case of `map`: fetching a property. _.pluck = function(obj, key) { - return _.map(obj, function(value){ return value[key]; }); + return _.map(obj, _.property(key)); }; // Convenience version of a common use case of `filter`: selecting only objects // containing specific `key:value` pairs. - _.where = function(obj, attrs, first) { - if (_.isEmpty(attrs)) return first ? void 0 : []; - return _[first ? 'find' : 'filter'](obj, function(value) { - for (var key in attrs) { - if (attrs[key] !== value[key]) return false; - } - return true; - }); + _.where = function(obj, attrs) { + return _.filter(obj, _.matches(attrs)); }; // Convenience version of a common use case of `find`: getting the first object // containing specific `key:value` pairs. _.findWhere = function(obj, attrs) { - return _.where(obj, attrs, true); + return _.find(obj, _.matches(attrs)); }; // Return the maximum element or (element-based computation). @@ -260,13 +255,15 @@ if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) { return Math.max.apply(Math, obj); } - if (!iterator && _.isEmpty(obj)) return -Infinity; - var result = {computed : -Infinity, value: -Infinity}; + var result = -Infinity, lastComputed = -Infinity; each(obj, function(value, index, list) { var computed = iterator ? iterator.call(context, value, index, list) : value; - computed > result.computed && (result = {value : value, computed : computed}); + if (computed > lastComputed) { + result = value; + lastComputed = computed; + } }); - return result.value; + return result; }; // Return the minimum element (or element-based computation). @@ -274,16 +271,18 @@ if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) { return Math.min.apply(Math, obj); } - if (!iterator && _.isEmpty(obj)) return Infinity; - var result = {computed : Infinity, value: Infinity}; + var result = Infinity, lastComputed = Infinity; each(obj, function(value, index, list) { var computed = iterator ? iterator.call(context, value, index, list) : value; - computed < result.computed && (result = {value : value, computed : computed}); + if (computed < lastComputed) { + result = value; + lastComputed = computed; + } }); - return result.value; + return result; }; - // Shuffle an array, using the modern version of the + // Shuffle an array, using the modern version of the // [Fisher-Yates shuffle](http://en.wikipedia.org/wiki/Fisher–Yates_shuffle). _.shuffle = function(obj) { var rand; @@ -297,11 +296,12 @@ return shuffled; }; - // Sample **n** random values from an array. - // If **n** is not specified, returns a single random element from the array. + // Sample **n** random values from a collection. + // If **n** is not specified, returns a single random element. // The internal `guard` argument allows it to work with `map`. _.sample = function(obj, n, guard) { - if (arguments.length < 2 || guard) { + if (n == null || guard) { + if (obj.length !== +obj.length) obj = _.values(obj); return obj[_.random(obj.length - 1)]; } return _.shuffle(obj).slice(0, Math.max(0, n)); @@ -309,12 +309,14 @@ // An internal function to generate lookup iterators. var lookupIterator = function(value) { - return _.isFunction(value) ? value : function(obj){ return obj[value]; }; + if (value == null) return _.identity; + if (_.isFunction(value)) return value; + return _.property(value); }; // Sort the object's values by a criterion produced by an iterator. - _.sortBy = function(obj, value, context) { - var iterator = lookupIterator(value); + _.sortBy = function(obj, iterator, context) { + iterator = lookupIterator(iterator); return _.pluck(_.map(obj, function(value, index, list) { return { value: value, @@ -334,9 +336,9 @@ // An internal function used for aggregate "group by" operations. var group = function(behavior) { - return function(obj, value, context) { + return function(obj, iterator, context) { var result = {}; - var iterator = value == null ? _.identity : lookupIterator(value); + iterator = lookupIterator(iterator); each(obj, function(value, index) { var key = iterator.call(context, value, index, obj); behavior(result, key, value); @@ -348,7 +350,7 @@ // Groups the object's values by a criterion. Pass either a string attribute // to group by, or a function that returns the criterion. _.groupBy = group(function(result, key, value) { - (_.has(result, key) ? result[key] : (result[key] = [])).push(value); + _.has(result, key) ? result[key].push(value) : result[key] = [value]; }); // Indexes the object's values by a criterion, similar to `groupBy`, but for @@ -367,7 +369,7 @@ // Use a comparator function to figure out the smallest index at which // an object should be inserted so as to maintain order. Uses binary search. _.sortedIndex = function(array, obj, iterator, context) { - iterator = iterator == null ? _.identity : lookupIterator(iterator); + iterator = lookupIterator(iterator); var value = iterator.call(context, obj); var low = 0, high = array.length; while (low < high) { @@ -399,7 +401,9 @@ // allows it to work with `_.map`. _.first = _.head = _.take = function(array, n, guard) { if (array == null) return void 0; - return (n == null) || guard ? array[0] : slice.call(array, 0, n); + if ((n == null) || guard) return array[0]; + if (n < 0) return []; + return slice.call(array, 0, n); }; // Returns everything but the last entry of the array. Especially useful on @@ -414,11 +418,8 @@ // values in the array. The **guard** check allows it to work with `_.map`. _.last = function(array, n, guard) { if (array == null) return void 0; - if ((n == null) || guard) { - return array[array.length - 1]; - } else { - return slice.call(array, Math.max(array.length - n, 0)); - } + if ((n == null) || guard) return array[array.length - 1]; + return slice.call(array, Math.max(array.length - n, 0)); }; // Returns everything but the first entry of the array. Aliased as `tail` and `drop`. @@ -459,6 +460,17 @@ return _.difference(array, slice.call(arguments, 1)); }; + // Split an array into two arrays: one whose elements all satisfy the given + // predicate, and one whose elements all do not satisfy the predicate. + _.partition = function(array, predicate, context) { + predicate = lookupIterator(predicate); + var pass = [], fail = []; + each(array, function(elem) { + (predicate.call(context, elem) ? pass : fail).push(elem); + }); + return [pass, fail]; + }; + // Produce a duplicate-free version of the array. If the array has already // been sorted, you have the option of using a faster algorithm. // Aliased as `unique`. @@ -492,7 +504,7 @@ var rest = slice.call(arguments, 1); return _.filter(_.uniq(array), function(item) { return _.every(rest, function(other) { - return _.indexOf(other, item) >= 0; + return _.contains(other, item); }); }); }; @@ -507,7 +519,7 @@ // Zip together multiple lists into a single array -- elements that share // an index go together. _.zip = function() { - var length = _.max(_.pluck(arguments, "length").concat(0)); + var length = _.max(_.pluck(arguments, 'length').concat(0)); var results = new Array(length); for (var i = 0; i < length; i++) { results[i] = _.pluck(arguments, '' + i); @@ -613,19 +625,27 @@ }; // Partially apply a function by creating a version that has had some of its - // arguments pre-filled, without changing its dynamic `this` context. + // arguments pre-filled, without changing its dynamic `this` context. _ acts + // as a placeholder, allowing any combination of arguments to be pre-filled. _.partial = function(func) { - var args = slice.call(arguments, 1); + var boundArgs = slice.call(arguments, 1); return function() { - return func.apply(this, args.concat(slice.call(arguments))); + var position = 0; + var args = boundArgs.slice(); + for (var i = 0, length = args.length; i < length; i++) { + if (args[i] === _) args[i] = arguments[position++]; + } + while (position < arguments.length) args.push(arguments[position++]); + return func.apply(this, args); }; }; - // Bind all of an object's methods to that object. Useful for ensuring that - // all callbacks defined on an object belong to it. + // Bind a number of an object's methods to that object. Remaining arguments + // are the method names to be bound. Useful for ensuring that all callbacks + // defined on an object belong to it. _.bindAll = function(obj) { var funcs = slice.call(arguments, 1); - if (funcs.length === 0) throw new Error("bindAll must be passed function names"); + if (funcs.length === 0) throw new Error('bindAll must be passed function names'); each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); }); return obj; }; @@ -664,12 +684,13 @@ var previous = 0; options || (options = {}); var later = function() { - previous = options.leading === false ? 0 : new Date; + previous = options.leading === false ? 0 : _.now(); timeout = null; result = func.apply(context, args); + context = args = null; }; return function() { - var now = new Date; + var now = _.now(); if (!previous && options.leading === false) previous = now; var remaining = wait - (now - previous); context = this; @@ -679,6 +700,7 @@ timeout = null; previous = now; result = func.apply(context, args); + context = args = null; } else if (!timeout && options.trailing !== false) { timeout = setTimeout(later, remaining); } @@ -692,24 +714,33 @@ // leading edge, instead of the trailing. _.debounce = function(func, wait, immediate) { var timeout, args, context, timestamp, result; + + var later = function() { + var last = _.now() - timestamp; + if (last < wait) { + timeout = setTimeout(later, wait - last); + } else { + timeout = null; + if (!immediate) { + result = func.apply(context, args); + context = args = null; + } + } + }; + return function() { context = this; args = arguments; - timestamp = new Date(); - var later = function() { - var last = (new Date()) - timestamp; - if (last < wait) { - timeout = setTimeout(later, wait - last); - } else { - timeout = null; - if (!immediate) result = func.apply(context, args); - } - }; + timestamp = _.now(); var callNow = immediate && !timeout; if (!timeout) { timeout = setTimeout(later, wait); } - if (callNow) result = func.apply(context, args); + if (callNow) { + result = func.apply(context, args); + context = args = null; + } + return result; }; }; @@ -731,11 +762,7 @@ // allowing you to adjust arguments, run code before and after, and // conditionally execute the original function. _.wrap = function(func, wrapper) { - return function() { - var args = [func]; - push.apply(args, arguments); - return wrapper.apply(this, args); - }; + return _.partial(wrapper, func); }; // Returns a function that is the composition of a list of functions, each @@ -765,8 +792,9 @@ // Retrieve the names of an object's properties. // Delegates to **ECMAScript 5**'s native `Object.keys` - _.keys = nativeKeys || function(obj) { - if (obj !== Object(obj)) throw new TypeError('Invalid object'); + _.keys = function(obj) { + if (!_.isObject(obj)) return []; + if (nativeKeys) return nativeKeys(obj); var keys = []; for (var key in obj) if (_.has(obj, key)) keys.push(key); return keys; @@ -921,7 +949,8 @@ // from different frames are. var aCtor = a.constructor, bCtor = b.constructor; if (aCtor !== bCtor && !(_.isFunction(aCtor) && (aCtor instanceof aCtor) && - _.isFunction(bCtor) && (bCtor instanceof bCtor))) { + _.isFunction(bCtor) && (bCtor instanceof bCtor)) + && ('constructor' in a && 'constructor' in b)) { return false; } // Add the first object to the stack of traversed objects. @@ -1061,6 +1090,30 @@ return value; }; + _.constant = function(value) { + return function () { + return value; + }; + }; + + _.property = function(key) { + return function(obj) { + return obj[key]; + }; + }; + + // Returns a predicate for checking whether an object has a given set of `key:value` pairs. + _.matches = function(attrs) { + return function(obj) { + if (obj === attrs) return true; //avoid comparing an object to itself. + for (var key in attrs) { + if (attrs[key] !== obj[key]) + return false; + } + return true; + } + }; + // Run a function **n** times. _.times = function(n, iterator, context) { var accum = Array(Math.max(0, n)); @@ -1077,6 +1130,9 @@ return min + Math.floor(Math.random() * (max - min + 1)); }; + // A (possibly faster) way to get the current timestamp as an integer. + _.now = Date.now || function() { return new Date().getTime(); }; + // List of HTML entities for escaping. var entityMap = { escape: { @@ -1273,4 +1329,16 @@ }); + // AMD registration happens at the end for compatibility with AMD loaders + // that may not enforce next-turn semantics on modules. Even though general + // practice for AMD registration is to be anonymous, underscore registers + // as a named module because, like jQuery, it is a base library that is + // popular enough to be bundled in a third party lib, but not be part of + // an AMD load request. Those cases could generate an error when an + // anonymous define() is called outside of a loader request. + if (typeof define === 'function' && define.amd) { + define('underscore', [], function() { + return _; + }); + } }).call(this); diff --git a/addons/web/static/src/css/base.css b/addons/web/static/src/css/base.css index 6fd9552dbe8..6972662130c 100644 --- a/addons/web/static/src/css/base.css +++ b/addons/web/static/src/css/base.css @@ -79,6 +79,20 @@ .openerp td { vertical-align: top; } +.openerp .oe_title { + width: 50%; + float: left; +} +.openerp .oe_title:after { + content: "."; + display: block; + height: 0; + clear: both; + visibility: hidden; +} +.openerp .oe_form_group { + clear: both; +} .openerp .zebra tbody tr:nth-child(odd) td { background-color: #f0f0fa; background-color: #efeff8; @@ -220,6 +234,9 @@ padding: 16px; } .openerp.ui-dialog .ui-dialog-titlebar { + border-top: none; + border-left: none; + border-right: none; border-bottom: 1px solid #cacaca; -moz-border-radius: 2px 2px 0 0; -webkit-border-radius: 2px 2px 0 0; @@ -236,9 +253,6 @@ margin: 0; padding: 0; } -.openerp.ui-dialog .ui-widget-header { - border: none; -} .openerp.ui-dialog .ui-dialog-content { background: white; } @@ -437,17 +451,6 @@ .openerp .oe_form_dirty button.oe_highlight_on_dirty:hover { background: #ed6f6a; } -.openerp .oe_title { - width: 50%; - float: left; -} -.openerp .oe_title:after { - content: "."; - display: block; - height: 0; - clear: both; - visibility: hidden; -} .openerp .oe_button_box { width: 270px; text-align: right; @@ -646,10 +649,6 @@ display: block; color: gray; } -.openerp .ui-tabs .oe_notebook.ui-tabs-nav li.ui-tabs-active { - border-bottom: none; - padding-bottom: 1px; -} .openerp .oe_notebook > li.ui-tabs-active > a { color: #4c4c4c; } @@ -675,6 +674,10 @@ background-color: #eeeeee; border-color: #eeeeee #eeeeee #dddddd; } +.openerp .ui-tabs .oe_notebook.ui-tabs-nav li.ui-tabs-active { + border-bottom: none; + padding-bottom: 1px; +} .openerp .oe_notebook > li.ui-state-active > a, .openerp .oe_notebook > li.ui-state-active > a:hover { background-color: white; border: 1px solid #dddddd; @@ -1047,7 +1050,7 @@ background-image: -moz-linear-gradient(top, #fc8787, maroon); background-image: -ms-linear-gradient(top, #fc8787, maroon); background-image: -o-linear-gradient(top, #fc8787, maroon); - background-image: linear-gradient(to bottom, #fc8787, maroon); + background-image: linear-gradient(to bottom, #fc8787, #800000); } .openerp .oe_topbar .oe_topbar_anonymous_login a { display: block; @@ -1293,7 +1296,7 @@ color: white; padding: 2px 4px; margin: 1px 6px 0 0; - border: 1px solid lightgrey; + border: 1px solid lightGray; text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2); -moz-border-radius: 4px; -webkit-border-radius: 4px; @@ -1325,7 +1328,7 @@ transform: scale(1.1); } .openerp .oe_secondary_submenu .oe_active { - border-top: 1px solid lightgrey; + border-top: 1px solid lightGray; border-bottom: 1px solid #dedede; text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2); -moz-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.2), inset 0 -1px 3px rgba(40, 40, 40, 0.2); @@ -1794,7 +1797,6 @@ border-radius: 0 3px 3px 0; } .openerp .oe_searchview .oe_searchview_facets .oe_searchview_facet .oe_facet_category, .openerp .oe_searchview .oe_searchview_facets .oe_searchview_facet .oe_facet_value { - height: 18px; padding: 0 4px; } .openerp .oe_searchview .oe_searchview_facets .oe_searchview_facet .oe_facet_category { @@ -2203,6 +2205,7 @@ } .openerp .oe_form header { position: relative; + overflow: hidden; border-bottom: 1px solid #cacaca; padding-left: 2px; background-color: #ededed; @@ -2328,7 +2331,7 @@ } .openerp .oe_form .oe_form_label_help[for] span, .openerp .oe_form .oe_form_label[for] span { font-size: 80%; - color: darkgreen; + color: darkGreen; vertical-align: top; position: relative; top: -4px; @@ -3185,6 +3188,7 @@ } .openerp .oe_debug_view_log { font-size: 95%; + line-height: 1.2em; } .openerp .oe_debug_view_log label { display: block; diff --git a/addons/web/static/src/css/base.sass b/addons/web/static/src/css/base.sass index d7f7e62f412..432374b1080 100644 --- a/addons/web/static/src/css/base.sass +++ b/addons/web/static/src/css/base.sass @@ -190,6 +190,17 @@ $sheet-padding: 16px vertical-align: middle td vertical-align: top + .oe_title + width: 50% + float: left + .oe_title:after + content: "." + display: block + height: 0 + clear: both + visibility: hidden + .oe_form_group + clear: both .zebra tbody tr:nth-child(odd) td background-color: #f0f0fa @include vertical-gradient(#f0f0fa, #eeeef6) @@ -277,14 +288,15 @@ $sheet-padding: 16px .ui-dialog-titlebar, .ui-dialog-content, .ui-dialog-buttonpane padding: 16px .ui-dialog-titlebar + border-top: none + border-left: none + border-right: none border-bottom: 1px solid #cacaca @include radius(2px 2px 0 0) @include vertical-gradient(#FCFCFC, #DEDEDE) .ui-dialog-title margin: 0 padding: 0 - .ui-widget-header - border: none .ui-dialog-content background: white .ui-dialog-buttonpane @@ -412,15 +424,6 @@ $sheet-padding: 16px @include box-shadow(none) &:hover background: #ED6F6A - .oe_title - width: 50% - float: left - .oe_title:after - content: "." - display: block - height: 0 - clear: both - visibility: hidden .oe_button_box width: 270px text-align: right @@ -1426,7 +1429,6 @@ $sheet-padding: 16px background: $tag-bg-light @include radius(0 3px 3px 0) .oe_facet_category, .oe_facet_value - height: 18px padding: 0 4px .oe_facet_category color: white @@ -1746,6 +1748,7 @@ $sheet-padding: 16px // FormView.header {{{ .oe_form header position: relative + overflow: hidden border-bottom: 1px solid #cacaca padding-left: 2px @include vertical-gradient(#fcfcfc, #dedede) @@ -2508,6 +2511,7 @@ $sheet-padding: 16px float: left .oe_debug_view_log font-size: 95% + line-height: 1.2em .oe_debug_view_log label display: block width: 49% diff --git a/addons/web/static/src/js/chrome.js b/addons/web/static/src/js/chrome.js index 90537d04009..4602019a11b 100644 --- a/addons/web/static/src/js/chrome.js +++ b/addons/web/static/src/js/chrome.js @@ -1488,10 +1488,16 @@ instance.web.WebClient = instance.web.Client.extend({ var state = $.bbq.getState(true); if (_.isEmpty(state) || state.action == "login") { self.menu.has_been_loaded.done(function() { - var first_menu_id = self.menu.$el.find("a:first").data("menu"); - if(first_menu_id) { - self.menu.menu_click(first_menu_id); - } + new instance.web.Model("res.users").call("read", [self.session.uid, ["action_id"]]).done(function(data) { + if(data.action_id) { + self.action_manager.do_action(data.action_id[0]); + self.menu.open_action(data.action_id[0]); + } else { + var first_menu_id = self.menu.$el.find("a:first").data("menu"); + if(first_menu_id) + self.menu.menu_click(first_menu_id); + } + }); }); } else { $(window).trigger('hashchange'); diff --git a/addons/web/static/src/js/data.js b/addons/web/static/src/js/data.js index fa276afa88f..36459575898 100644 --- a/addons/web/static/src/js/data.js +++ b/addons/web/static/src/js/data.js @@ -447,6 +447,9 @@ instance.web.DataSet = instance.web.Class.extend(instance.web.PropertiesMixin, * @returns {$.Deferred} */ read_ids: function (ids, fields, options) { + if (_.isEmpty(ids)) + return $.Deferred().resolve([]); + options = options || {}; // TODO: reorder results to match ids list return this._model.call('read', @@ -867,7 +870,8 @@ instance.web.BufferedDataSet = instance.web.DataSetStatic.extend({ }); var return_records = function() { var records = _.map(ids, function(id) { - return _.extend({}, _.detect(self.cache, function(c) {return c.id === id;}).values, {"id": id}); + var c = _.find(self.cache, function(c) {return c.id === id;}); + return _.isUndefined(c) ? c : _.extend({}, c.values, {"id": id}); }); if (self.debug_mode) { if (_.include(records, undefined)) { @@ -895,6 +899,10 @@ instance.web.BufferedDataSet = instance.web.DataSetStatic.extend({ sign = -1; field = field.slice(1); } + //m2o should be searched based on value[1] not based whole value(i.e. [id, value]) + if(_.isArray(a[field]) && a[field].length == 2 && _.isString(a[field][1])){ + return sign * compare(a[field][1], b[field][1]); + } return sign * compare(a[field], b[field]); }, 0); }); diff --git a/addons/web/static/src/js/data_export.js b/addons/web/static/src/js/data_export.js index 2725e18ea6e..252a5a378cc 100644 --- a/addons/web/static/src/js/data_export.js +++ b/addons/web/static/src/js/data_export.js @@ -119,6 +119,7 @@ instance.web.DataExport = instance.web.Dialog.extend({ if (select_exp.val()) { self.exports.unlink([parseInt(select_exp.val(), 10)]); select_exp.remove(); + self.$el.find("#fields_list option").remove(); if (self.$el.find('#saved_export_list option').length <= 1) { self.$el.find('#ExistsExportList').hide(); } @@ -166,18 +167,16 @@ instance.web.DataExport = instance.web.Dialog.extend({ export_fields: _(fields).map(function (field) { return [0, 0, {name: field}]; }) - }, function (export_list_id) { - if (!export_list_id.result) { + }).then(function (export_list_id) { + if (!export_list_id) { return; } - self.$el.find("#saved_export_list").append( - new Option(value, export_list_id.result)); - if (self.$el.find("#saved_export_list").is(":hidden")) { + if (!self.$el.find("#saved_export_list").length || self.$el.find("#saved_export_list").is(":hidden")) { self.show_exports_list(); } + self.$el.find("#saved_export_list").append( new Option(value, export_list_id) ); }); this.on_show_save_list(); - this.$el.find("#fields_list option").remove(); }, on_click: function(id, record) { var self = this; diff --git a/addons/web/static/src/js/formats.js b/addons/web/static/src/js/formats.js index 17df1f3e6d1..2f6bbcebc4f 100644 --- a/addons/web/static/src/js/formats.js +++ b/addons/web/static/src/js/formats.js @@ -141,7 +141,7 @@ instance.web.format_value = function (value, descriptor, value_if_empty) { //noinspection FallthroughInSwitchStatementJS switch (value) { case '': - if (descriptor.type === 'char') { + if (descriptor.type === 'char' || descriptor.type === 'text') { return ''; } console.warn('Field', descriptor, 'had an empty string as value, treating as false...'); @@ -196,7 +196,7 @@ instance.web.format_value = function (value, descriptor, value_if_empty) { + ' ' + normalize_format(l10n.time_format)); case 'date': if (typeof(value) == "string") - value = instance.web.auto_str_to_date(value); + value = instance.web.str_to_date(value.substring(0,10)); return value.toString(normalize_format(l10n.date_format)); case 'time': if (typeof(value) == "string") diff --git a/addons/web/static/src/js/search.js b/addons/web/static/src/js/search.js index 8c9411abd76..535a5de894c 100644 --- a/addons/web/static/src/js/search.js +++ b/addons/web/static/src/js/search.js @@ -348,7 +348,7 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea } }, 'autocompleteopen': function () { - this.$el.autocomplete('widget').css('z-index', 1004); + this.$el.autocomplete('widget').css('z-index', 9999); }, }, /** @@ -407,7 +407,7 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea context: this.dataset.get_context(), }); - $.when(load_view).then(function (r) { + this.alive($.when(load_view)).then(function (r) { return self.search_view_loaded(r); }).fail(function () { self.ready.reject.apply(null, arguments); @@ -466,19 +466,20 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea * Sets up search view's view-wide auto-completion widget */ setup_global_completion: function () { - var self = this; - var autocomplete = this.$el.autocomplete({ source: this.proxy('complete_global_search'), select: this.proxy('select_completion'), - search: function () { self.$el.autocomplete('close'); }, focus: function (e) { e.preventDefault(); }, html: true, autoFocus: true, minLength: 1, - delay: 0, + delay: 250, }).data('autocomplete'); + this.$el.on('input', function () { + this.$el.autocomplete('close'); + }.bind(this)); + // MonkeyPatch autocomplete instance _.extend(autocomplete, { _renderItem: function (ul, item) { diff --git a/addons/web/static/src/js/view_form.js b/addons/web/static/src/js/view_form.js index 099a094a581..89e25ff05af 100644 --- a/addons/web/static/src/js/view_form.js +++ b/addons/web/static/src/js/view_form.js @@ -3085,7 +3085,8 @@ instance.web.form.CompletionFieldMixin = { if (self.options.quick_create === undefined || self.options.quick_create) { new instance.web.DataSet(this, this.field.relation, self.build_context()) .name_create(name).done(function(data) { - self.add_id(data[0]); + if (!self.get('effective_readonly')) + self.add_id(data[0]); }).fail(function(error, event) { event.preventDefault(); slow_create(); @@ -3389,7 +3390,7 @@ instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(instanc // disabled to solve a bug, but may cause others //close: anyoneLoosesFocus, minLength: 0, - delay: 0 + delay: 250 }); this.$input.autocomplete("widget").openerpClass(); // used to correct a bug when selecting an element by pushing 'enter' in an editable list @@ -4926,7 +4927,7 @@ instance.web.form.SelectCreatePopup = instance.web.form.AbstractFormPopup.extend 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, groupbys); + contexts.concat(self.context), groupbys); self.initial_ids = undefined; } else { self.do_search(domains.concat([self.domain]), contexts.concat(self.context), groupbys); @@ -5051,7 +5052,7 @@ instance.web.form.FieldReference = instance.web.form.AbstractField.extend(instan .on('blurred', null, function () {self.trigger('blurred');}); this.m2o = new instance.web.form.FieldMany2One(fm, { attrs: { - name: 'm2o', + name: 'Referenced Document', modifiers: JSON.stringify({readonly: this.get('effective_readonly')}), }}); this.m2o.on("change:value", this, this.data_changed); @@ -5487,9 +5488,6 @@ instance.web.form.FieldStatus = instance.web.form.AbstractField.extend({ if (this.options.clickable) { this.$el.on('click','li[data-id]',this.on_click_stage); } - if (this.$el.parent().is('header')) { - this.$el.after('
'); - } this._super(); }, set_value: function(value_) { diff --git a/addons/web/static/src/js/view_list.js b/addons/web/static/src/js/view_list.js index 89f69c38251..0ab727c3232 100644 --- a/addons/web/static/src/js/view_list.js +++ b/addons/web/static/src/js/view_list.js @@ -408,6 +408,9 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi if (total) { var range_start = this.page * limit + 1; var range_stop = range_start - 1 + limit; + if (this.records.length) { + range_stop = range_start - 1 + this.records.length; + } if (range_stop > total) { range_stop = total; } @@ -602,7 +605,17 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi _(ids).each(function (id) { self.records.remove(self.records.get(id)); }); - self.configure_pager(self.dataset); + if (self.records.length === 0 && self.dataset.size() > 0) { + //Trigger previous manually to navigate to previous page, + //If all records are deleted on current page. + self.$pager.find('ul li:first a').trigger('click'); + } else if (self.dataset.size() == self.limit()) { + //Reload listview to update current page with next page records + //because pager going to be hidden if dataset.size == limit + self.reload(); + } else { + self.configure_pager(self.dataset); + } self.compute_aggregates(); }); }, @@ -1658,9 +1671,12 @@ instance.web.ListView.Groups = instance.web.Class.extend( /** @lends instance.we function synchronized(fn) { var fn_mutex = new $.Mutex(); return function () { + var obj = this; var args = _.toArray(arguments); - args.unshift(this); - return fn_mutex.exec(fn.bind.apply(fn, args)); + return fn_mutex.exec(function () { + if (obj.isDestroyed()) { return $.when(); } + return fn.apply(obj, args) + }); }; } var DataGroup = instance.web.Class.extend({ diff --git a/addons/web/static/src/js/views.js b/addons/web/static/src/js/views.js index c9584002472..2ec06bb11a7 100644 --- a/addons/web/static/src/js/views.js +++ b/addons/web/static/src/js/views.js @@ -1227,7 +1227,7 @@ instance.web.Sidebar = instance.web.Widget.extend({ this.dataset = dataset; this.model_id = model_id; if (args && args[0].error) { - this.do_warn( instance.web.qweb.render('message_error_uploading'), args[0].error); + this.do_warn(_t('Uploading Error'), args[0].error); } if (!model_id) { this.on_attachments_loaded([]); @@ -1311,7 +1311,7 @@ instance.web.View = instance.web.Widget.extend({ "context": this.dataset.get_context(), }); } - return view_loaded_def.then(function(r) { + 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') diff --git a/addons/web/tests/test_dataset.py b/addons/web/tests/test_dataset.py index 98c2f95447c..db75703d3e9 100644 --- a/addons/web/tests/test_dataset.py +++ b/addons/web/tests/test_dataset.py @@ -28,6 +28,11 @@ class TestDataSetController(unittest2.TestCase): def test_regular_find(self): self.search.return_value = [1, 2, 3] + self.read.return_value = [ + {'id': 1, 'name': 'foo'}, + {'id': 2, 'name': 'bar'}, + {'id': 3, 'name': 'qux'} + ] self.dataset.do_search_read('fake.model') self.read.assert_called_once_with( diff --git a/addons/web_calendar/static/src/js/calendar.js b/addons/web_calendar/static/src/js/calendar.js index a0945836fd6..f7f08c3f6e3 100644 --- a/addons/web_calendar/static/src/js/calendar.js +++ b/addons/web_calendar/static/src/js/calendar.js @@ -340,6 +340,7 @@ instance.web_calendar.CalendarView = instance.web.View.extend({ return evt[fld][1]; return evt[fld]; }); + res_text = _.filter(res_text, function(e){return !_.isEmpty(e);}); } if (!date_stop && date_delay) { date_stop = date_start.clone().addHours(date_delay); diff --git a/addons/web_kanban/static/src/js/kanban.js b/addons/web_kanban/static/src/js/kanban.js index 1de3df275c6..f0568ccf7fc 100644 --- a/addons/web_kanban/static/src/js/kanban.js +++ b/addons/web_kanban/static/src/js/kanban.js @@ -555,7 +555,7 @@ instance.web_kanban.KanbanGroup = instance.web.Widget.extend({ } catch(e) {} } _.each(this.view.aggregates, function(value, key) { - self.aggregates[value] = group.get('aggregates')[key]; + self.aggregates[value] = instance.web.format_value(group.get('aggregates')[key], {type: 'float'}); }); }