[MERGE] Latest bugfixes from 7.0, up to rev 3731

rev 3731 = revision-id: fme@openerp.com-20130129142638-81uffjq3aiopr8ta

bzr revid: odo@openerp.com-20130129163725-feesf1n2xf0wbpro
This commit is contained in:
Olivier Dony 2013-01-29 17:37:25 +01:00
commit 02a6c638a9
15 changed files with 297 additions and 70 deletions

View File

@ -86,5 +86,4 @@ This module provides the core of the OpenERP Web Client.
"static/test/mutex.js"
],
'bootstrap': True,
'twitter': False,
}

View File

@ -522,7 +522,7 @@ html_template = """<!DOCTYPE html>
</head>
<body>
<!--[if lte IE 8]>
<script src="http://ajax.googleapis.com/ajax/libs/chrome-frame/1/CFInstall.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/chrome-frame/1/CFInstall.min.js"></script>
<script>CFInstall.check({mode: "overlay"});</script>
<![endif]-->
</body>

View File

@ -1,3 +1,5 @@
.. _module:
Building an OpenERP Web module
==============================

View File

@ -85,7 +85,7 @@ class OpenERPSession(object):
self.jsonp_requests = {} # FIXME use a LRU
def send(self, service_name, method, *args):
code_string = "warning -- %s\n\n%s"
code_string = u"warning -- %s\n\n%s"
try:
return openerp.netsvc.dispatch_rpc(service_name, method, args)
except openerp.osv.osv.except_osv, e:
@ -95,13 +95,13 @@ class OpenERPSession(object):
except openerp.exceptions.AccessError, e:
raise xmlrpclib.Fault(code_string % ("AccessError", e), '')
except openerp.exceptions.AccessDenied, e:
raise xmlrpclib.Fault('AccessDenied', str(e))
raise xmlrpclib.Fault('AccessDenied', openerp.tools.ustr(e))
except openerp.exceptions.DeferredException, e:
formatted_info = "".join(traceback.format_exception(*e.traceback))
raise xmlrpclib.Fault(openerp.tools.ustr(e.message), formatted_info)
raise xmlrpclib.Fault(openerp.tools.ustr(e), formatted_info)
except Exception, e:
formatted_info = "".join(traceback.format_exception(*(sys.exc_info())))
raise xmlrpclib.Fault(openerp.tools.exception_to_unicode(e), formatted_info)
raise xmlrpclib.Fault(openerp.tools.ustr(e), formatted_info)
def proxy(self, service):
return Service(self, service)

View File

@ -0,0 +1,195 @@
Date.CultureInfo = {
/* Culture Name */
name: "am-ET",
englishName: "Amharic (Ethiopia)",
nativeName: "አምሃርኛ (ኢትዮጵያ)",
/* Day Name Strings */
dayNames: ["እሁድ", "ሰኞ", "ማክሰኞ", "ረብዑ", "ሃሙስ", "ዓርብ", "ቅዳሜ"],
abbreviatedDayNames: ["እሁድ", "ሰኞ", "ማክሰ", "ረብዑ", "ሃሙስ", "ዓርብ", "ቅዳሜ"],
shortestDayNames: ["እሁ", "ሰኞ", "ማክ", "ረብ", "ሃሙ", "ዓር", "ቅዳ"],
firstLetterDayNames: ["እ", "ሰ", "ማ", "ረ", "ሃ", "ዓ", "ቅ"],
/* Month Name Strings */
monthNames: ["ጃንዋሪ", "ፌብሩዋሪ", "ማርች", "አፕሪል", "ሜይ", "ጁን", "ጁላይ", "ኦገስት", "ሴፕቴምበር", "ኦክቶበር", "ኖቬምበር", "ዲሴምበር"],
abbreviatedMonthNames: ["ጃንዋ", "ፌብሩ", "ማርች", "አፕሪ", "ሜይ", "ጁን", "ጁላይ", "ኦገስ", "ሴፕቴ", "ኦክቶ", "ኖቬም", "ዲሴም"],
/* AM/PM Designators */
amDesignator: "ከቀኑ",
pmDesignator: "ከለሊቱ",
firstDayOfWeek: 1,
twoDigitYearMax: 2029,
/**
* The dateElementOrder is based on the order of the
* format specifiers in the formatPatterns.DatePattern.
*
* Example:
<pre>
shortDatePattern dateElementOrder
------------------ ----------------
"M/d/yyyy" "mdy"
"dd/MM/yyyy" "dmy"
"yyyy-MM-dd" "ymd"
</pre>
*
* The correct dateElementOrder is required by the parser to
* determine the expected order of the date elements in the
* string being parsed.
*/
dateElementOrder: "dmy",
/* Standard date and time format patterns */
formatPatterns: {
shortDate: "dd/MM/yyyy",
longDate: "dd MMMM yyyy",
shortTime: "HH:mm",
longTime: "HH:mm:ss",
fullDateTime: "dd MMMM yyyy HH:mm:ss",
sortableDateTime: "yyyy-MM-ddTHH:mm:ss",
universalSortableDateTime: "yyyy-MM-dd HH:mm:ssZ",
rfc1123: "ddd, dd MMM yyyy HH:mm:ss GMT",
monthDay: "dd MMMM",
yearMonth: "MMMM yyyy"
},
/**
* NOTE: If a string format is not parsing correctly, but
* you would expect it parse, the problem likely lies below.
*
* The following regex patterns control most of the string matching
* within the parser.
*
* The Month name and Day name patterns were automatically generated
* and in general should be (mostly) correct.
*
* Beyond the month and day name patterns are natural language strings.
* Example: "next", "today", "months"
*
* These natural language string may NOT be correct for this culture.
* If they are not correct, please translate and edit this file
* providing the correct regular expression pattern.
*
* If you modify this file, please post your revised CultureInfo file
* to the Datejs Forum located at http://www.datejs.com/forums/.
*
* Please mark the subject of the post with [CultureInfo]. Example:
* Subject: [CultureInfo] Translated "da-DK" Danish(Denmark)
*
* We will add the modified patterns to the master source files.
*
* As well, please review the list of "Future Strings" section below.
*/
regexPatterns: {
jan: /^jan(uary)?/i,
feb: /^feb(ruary)?/i,
mar: /^mar(ch)?/i,
apr: /^apr(il)?/i,
may: /^may/i,
jun: /^jun(e)?/i,
jul: /^jul(y)?/i,
aug: /^aug(ust)?/i,
sep: /^sep(t(ember)?)?/i,
oct: /^oct(ober)?/i,
nov: /^nov(ember)?/i,
dec: /^dec(ember)?/i,
sun: /^su(n(day)?)?/i,
mon: /^mo(n(day)?)?/i,
tue: /^tu(e(s(day)?)?)?/i,
wed: /^we(d(nesday)?)?/i,
thu: /^th(u(r(s(day)?)?)?)?/i,
fri: /^fr(i(day)?)?/i,
sat: /^sa(t(urday)?)?/i,
future: /^next/i,
past: /^last|past|prev(ious)?/i,
add: /^(\+|aft(er)?|from|hence)/i,
subtract: /^(\-|bef(ore)?|ago)/i,
yesterday: /^yes(terday)?/i,
today: /^t(od(ay)?)?/i,
tomorrow: /^tom(orrow)?/i,
now: /^n(ow)?/i,
millisecond: /^ms|milli(second)?s?/i,
second: /^sec(ond)?s?/i,
minute: /^mn|min(ute)?s?/i,
hour: /^h(our)?s?/i,
week: /^w(eek)?s?/i,
month: /^m(onth)?s?/i,
day: /^d(ay)?s?/i,
year: /^y(ear)?s?/i,
shortMeridian: /^(a|p)/i,
longMeridian: /^(a\.?m?\.?|p\.?m?\.?)/i,
timezone: /^((e(s|d)t|c(s|d)t|m(s|d)t|p(s|d)t)|((gmt)?\s*(\+|\-)\s*\d\d\d\d?)|gmt|utc)/i,
ordinalSuffix: /^\s*(st|nd|rd|th)/i,
timeContext: /^\s*(\:|a(?!u|p)|p)/i
},
timezones: [{name:"UTC", offset:"-000"}, {name:"GMT", offset:"-000"}, {name:"EST", offset:"-0500"}, {name:"EDT", offset:"-0400"}, {name:"CST", offset:"-0600"}, {name:"CDT", offset:"-0500"}, {name:"MST", offset:"-0700"}, {name:"MDT", offset:"-0600"}, {name:"PST", offset:"-0800"}, {name:"PDT", offset:"-0700"}]
};
/********************
** Future Strings **
********************
*
* The following list of strings may not be currently being used, but
* may be incorporated into the Datejs library later.
*
* We would appreciate any help translating the strings below.
*
* If you modify this file, please post your revised CultureInfo file
* to the Datejs Forum located at http://www.datejs.com/forums/.
*
* Please mark the subject of the post with [CultureInfo]. Example:
* Subject: [CultureInfo] Translated "da-DK" Danish(Denmark)b
*
* English Name Translated
* ------------------ -----------------
* about about
* ago ago
* date date
* time time
* calendar calendar
* show show
* hourly hourly
* daily daily
* weekly weekly
* bi-weekly bi-weekly
* fortnight fortnight
* monthly monthly
* bi-monthly bi-monthly
* quarter quarter
* quarterly quarterly
* yearly yearly
* annual annual
* annually annually
* annum annum
* again again
* between between
* after after
* from now from now
* repeat repeat
* times times
* per per
* min (abbrev minute) min
* morning morning
* noon noon
* night night
* midnight midnight
* mid-night mid-night
* evening evening
* final final
* future future
* spring spring
* summer summer
* fall fall
* winter winter
* end of end of
* end end
* long long
* short short
*/

View File

@ -1242,8 +1242,6 @@
.openerp .oe_secondary_submenu {
padding: 2px 0 8px 0;
margin: 0;
width: 220px;
display: inline-block;
}
.openerp .oe_secondary_submenu li {
position: relative;

View File

@ -1009,8 +1009,6 @@ $sheet-padding: 16px
.oe_secondary_submenu
padding: 2px 0 8px 0
margin: 0
width: 220px
display: inline-block
li
position: relative
margin: 0

View File

@ -481,6 +481,7 @@ instance.web.DatabaseManager = instance.web.Widget.extend({
self.do_action("reload");
},
},
_push_me: false,
};
self.do_action(client_action);
});
@ -868,7 +869,7 @@ instance.web.Menu = instance.web.Widget.extend({
this.needaction_data = data;
_.each(this.needaction_data, function (item, menu_id) {
var $item = self.$secondary_menus.find('a[data-menu="' + menu_id + '"]');
$item.remove('oe_menu_counter');
$item.find('.oe_menu_counter').remove();
if (item.needaction_counter && item.needaction_counter > 0) {
$item.append(QWeb.render("Menu.needaction_counter", { widget : item }));
}
@ -1395,13 +1396,9 @@ instance.web.WebClient = instance.web.Client.extend({
});
},
set_content_full_screen: function(fullscreen) {
if (fullscreen) {
$(".oe_webclient", this.$el).addClass("oe_content_full_screen");
$("body").css({'overflow-y':'hidden'});
} else {
$(".oe_webclient", this.$el).removeClass("oe_content_full_screen");
$("body").css({'overflow-y':'scroll'});
}
$(document.body).css('overflow-y', fullscreen ? 'hidden' : 'scroll');
this.$('.oe_webclient').toggleClass(
'oe_content_full_screen', fullscreen);
},
has_uncommitted_changes: function() {
var $e = $.Event('clear_uncommitted_changes');

View File

@ -166,9 +166,13 @@ instance.web.format_value = function (value, descriptor, value_if_empty) {
value = Math.abs(value);
pattern = '-' + pattern;
}
return _.str.sprintf(pattern,
Math.floor(value),
Math.round((value % 1) * 60));
var hour = Math.floor(value);
var min = Math.round((value % 1) * 60);
if (min == 60){
min = 0;
hour = hour + 1;
}
return _.str.sprintf(pattern, hour, min);
case 'many2one':
// name_get value format
return value[1] ? value[1].split("\n")[0] : value[1];

View File

@ -689,15 +689,14 @@ openerp.web.pyeval = function (instance) {
};
instance.web.pyeval.context = function () {
return {
uid: py.float.fromJSON(instance.session.uid),
return _.extend({
datetime: datetime,
context_today: context_today,
time: time,
relativedelta: relativedelta,
current_date: py.PY_call(
time.strftime, [py.str.fromJSON('%Y-%m-%d')]),
};
}, instance.session.user_context);
};
/**

View File

@ -127,41 +127,26 @@ my.InputView = instance.web.Widget.extend({
events: {
focus: function () { this.trigger('focused', this); },
blur: function () { this.$el.text(''); this.trigger('blurred', this); },
keydown: 'onKeydown'
keydown: 'onKeydown',
paste: 'onPaste',
},
getSelection: function () {
// get Text node
var root = this.$el[0].childNodes[0];
var root = this.el.childNodes[0];
if (!root || !root.textContent) {
// if input does not have a child node, or the child node is an
// empty string, then the selection can only be (0, 0)
return {start: 0, end: 0};
}
if (window.getSelection) {
var domRange = window.getSelection().getRangeAt(0);
assert(domRange.startContainer === root,
"selection should be in the input view");
assert(domRange.endContainer === root,
"selection should be in the input view");
return {
start: domRange.startOffset,
end: domRange.endOffset
}
} else if (document.selection) {
var ieRange = document.selection.createRange();
var rangeParent = ieRange.parentElement();
assert(rangeParent === root,
"selection should be in the input view");
var offsetRange = document.body.createTextRange();
offsetRange = offsetRange.moveToElementText(rangeParent);
offsetRange.setEndPoint("EndToStart", ieRange);
var start = offsetRange.text.length;
return {
start: start,
end: start + ieRange.text.length
}
var range = window.getSelection().getRangeAt(0);
assert(range.startContainer === root,
"selection should be in the input view");
assert(range.endContainer === root,
"selection should be in the input view");
return {
start: range.startOffset,
end: range.endOffset
}
throw new Error("Could not get caret position");
},
onKeydown: function (e) {
var sel;
@ -199,6 +184,50 @@ my.InputView = instance.web.Widget.extend({
}
break;
}
},
setCursorAtEnd: function () {
var sel = window.getSelection();
sel.removeAllRanges();
var range = document.createRange();
// in theory, range.selectNodeContents should work here. In practice,
// MSIE9 has issues from time to time, instead of selecting the inner
// text node it would select the reference node instead (e.g. in demo
// data, company news, copy across the "Company News" link + the title,
// from about half the link to half the text, paste in search box then
// hit the left arrow key, getSelection would blow up).
//
// Explicitly selecting only the inner text node (only child node at
// this point, though maybe we should assert that) avoiids the issue
range.selectNode(this.el.childNodes[0]);
range.collapse(false);
sel.addRange(range);
},
onPaste: function () {
// In MSIE and Webkit, it is possible to get various representations of
// the clipboard data at this point e.g.
// window.clipboardData.getData('Text') and
// event.clipboardData.getData('text/plain') to ensure we have a plain
// text representation of the object (and probably ensure the object is
// pastable as well, so nobody puts an image in the search view)
// (nb: since it's not possible to alter the content of the clipboard
// — at least in Webkit — to ensure only textual content is available,
// using this would require 1. getting the text data; 2. manually
// inserting the text data into the content; and 3. cancelling the
// paste event)
//
// But Firefox doesn't support the clipboard API (as of FF18)
// although it correctly triggers the paste event (Opera does not even
// do that) => implement lowest-denominator system where onPaste
// triggers a followup "cleanup" pass after the data has been pasted
setTimeout(function () {
// Read text content (ignore pasted HTML)
var data = this.$el.text();
// paste raw text back in
this.$el.empty().text(data);
// Set the cursor at the end of the text, so the cursor is not lost
// in some kind of error-spawning limbo.
this.setCursorAtEnd();
}.bind(this), 0);
}
});
my.FacetView = instance.web.Widget.extend({
@ -1572,6 +1601,7 @@ instance.web.search.CustomFilters = instance.web.search.Input.extend({
append_filter: function (filter) {
var self = this;
var key = this.key_for(filter);
var warning = _t("This filter is global and will be removed for everybody if you continue.");
var $filter;
if (key in this.$filters) {
@ -1589,6 +1619,9 @@ instance.web.search.CustomFilters = instance.web.search.Input.extend({
$('<a class="oe_searchview_custom_delete">x</a>')
.click(function (e) {
e.stopPropagation();
if (!(filter.user_id || confirm(warning))) {
return;
}
self.model.call('unlink', [id]).done(function () {
$filter.remove();
delete self.$filters[key];
@ -1711,10 +1744,12 @@ instance.web.search.Advanced = instance.web.search.Input.extend({
});
return $.when(
this._super(),
new instance.web.Model(this.view.model).call('fields_get').done(function(data) {
self.fields = _.extend({
id: { string: 'ID', type: 'id' }
}, data);
new instance.web.Model(this.view.model).call('fields_get', {
context: this.view.dataset.context
}).done(function(data) {
self.fields = _.extend({
id: { string: 'ID', type: 'id' }
}, data);
})).done(function () {
self.append_proposition();
});
@ -1781,6 +1816,7 @@ instance.web.search.ExtendedSearchProposition = instance.web.Widget.extend(/** @
this._super(parent);
this.fields = _(fields).chain()
.map(function(val, key) { return _.extend({}, val, {'name': key}); })
.filter(function (field) { return !field.deprecated; })
.sortBy(function(field) {return field.string;})
.value();
this.attrs = {_: _, fields: this.fields, selected: null};

View File

@ -573,19 +573,14 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
]
});
}
if (result.domain) {
function edit_domain(node) {
if (typeof node !== "object") {
return;
}
var new_domain = result.domain[node.attrs.name];
if (new_domain) {
node.attrs.domain = new_domain;
}
_(node.children).each(edit_domain);
}
edit_domain(this.fields_view.arch);
}
var fields = this.fields;
_(result.domain).each(function (domain, fieldname) {
var field = fields[fieldname];
if (!field) { return; }
field.node.attrs.domain = domain;
});
return $.Deferred().resolve();
} catch(e) {
console.error(e);

View File

@ -1414,8 +1414,6 @@
this.removeAttr('t-if');
</t>
<t t-jquery="tfoot &gt; tr:last-child" t-operation="replace"/>
<t t-jquery="td.oe-actions">
this.removeAttr('t-if');
</t>

View File

@ -68,6 +68,12 @@ openerp.testing.section('web-formats', {
strictEqual(
instance.web.format_value(-0.0085, {type:'float', widget:'float_time'}),
'-00:01');
strictEqual(
instance.web.format_value(4.9999, {type:'float', widget:'float_time'}),
'05:00');
strictEqual(
instance.web.format_value(-6.9999, {type:'float', widget:'float_time'}),
'-07:00');
});
test("format_float", function (instance) {
var fl = 12.1234;

View File

@ -632,7 +632,7 @@ instance.web_kanban.KanbanGroup = instance.web.Widget.extend({
'limit': self.view.limit,
'offset': self.dataset_offset += self.view.limit
}).then(function(records) {
self.view.dataset.ids = ids.concat(self.view.dataset.ids);
self.view.dataset.ids = ids.concat(self.dataset.ids);
self.do_add_records(records);
self.compute_cards_auto_height();
return records;