[MERGE] from trunk
bzr revid: chm@openerp.com-20130131093733-9k2p09hqmsxmo35l
This commit is contained in:
commit
d8f18175b9
|
@ -86,5 +86,4 @@ This module provides the core of the OpenERP Web Client.
|
|||
"static/test/mutex.js"
|
||||
],
|
||||
'bootstrap': True,
|
||||
'twitter': False,
|
||||
}
|
||||
|
|
|
@ -14,7 +14,6 @@ import re
|
|||
import simplejson
|
||||
import time
|
||||
import urllib2
|
||||
import xmlrpclib
|
||||
import zlib
|
||||
from xml.etree import ElementTree
|
||||
from cStringIO import StringIO
|
||||
|
@ -97,7 +96,7 @@ def db_monodb(req):
|
|||
dbs = db_list(req)
|
||||
if len(dbs) == 1:
|
||||
return dbs[0]
|
||||
except xmlrpclib.Fault:
|
||||
except Exception:
|
||||
# ignore access denied
|
||||
pass
|
||||
return False
|
||||
|
@ -522,7 +521,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>
|
||||
|
@ -749,10 +748,10 @@ class Database(openerpweb.Controller):
|
|||
|
||||
try:
|
||||
return req.session.proxy("db").drop(password, db)
|
||||
except xmlrpclib.Fault, e:
|
||||
if e.faultCode and e.faultCode.split(':')[0] == 'AccessDenied':
|
||||
return {'error': e.faultCode, 'title': 'Drop Database'}
|
||||
return {'error': _('Could not drop database !'), 'title': _('Drop Database')}
|
||||
except openerp.exceptions.AccessDenied:
|
||||
return {'error': 'AccessDenied', 'title': 'Drop Database'}
|
||||
except Exception:
|
||||
return {'error': _('Could not drop database !'), 'title': _('Drop Database')}
|
||||
|
||||
@openerpweb.httprequest
|
||||
def backup(self, req, backup_db, backup_pwd, token):
|
||||
|
@ -769,8 +768,8 @@ class Database(openerpweb.Controller):
|
|||
('Content-Disposition', content_disposition(filename, req))],
|
||||
{'fileToken': int(token)}
|
||||
)
|
||||
except xmlrpclib.Fault, e:
|
||||
return simplejson.dumps([[],[{'error': e.faultCode, 'title': _('Backup Database')}]])
|
||||
except Exception, e:
|
||||
return simplejson.dumps([[],[{'error': openerp.tools.ustr(e), 'title': _('Backup Database')}]])
|
||||
|
||||
@openerpweb.httprequest
|
||||
def restore(self, req, db_file, restore_pwd, new_db):
|
||||
|
@ -778,9 +777,8 @@ class Database(openerpweb.Controller):
|
|||
data = base64.b64encode(db_file.read())
|
||||
req.session.proxy("db").restore(restore_pwd, new_db, data)
|
||||
return ''
|
||||
except xmlrpclib.Fault, e:
|
||||
if e.faultCode and e.faultCode.split(':')[0] == 'AccessDenied':
|
||||
raise Exception("AccessDenied")
|
||||
except openerp.exceptions.AccessDenied, e:
|
||||
raise Exception("AccessDenied")
|
||||
|
||||
@openerpweb.jsonrequest
|
||||
def change_password(self, req, fields):
|
||||
|
@ -789,10 +787,10 @@ class Database(openerpweb.Controller):
|
|||
dict(map(operator.itemgetter('name', 'value'), fields)))
|
||||
try:
|
||||
return req.session.proxy("db").change_admin_password(old_password, new_password)
|
||||
except xmlrpclib.Fault, e:
|
||||
if e.faultCode and e.faultCode.split(':')[0] == 'AccessDenied':
|
||||
return {'error': e.faultCode, 'title': _('Change Password')}
|
||||
return {'error': _('Error, password not changed !'), 'title': _('Change Password')}
|
||||
except openerp.exceptions.AccessDenied:
|
||||
return {'error': 'AccessDenied', 'title': _('Change Password')}
|
||||
except Exception:
|
||||
return {'error': _('Error, password not changed !'), 'title': _('Change Password')}
|
||||
|
||||
class Session(openerpweb.Controller):
|
||||
_cp_path = "/web/session"
|
||||
|
@ -1194,7 +1192,7 @@ class Binary(openerpweb.Controller):
|
|||
|
||||
image_data = base64.b64decode(image_base64)
|
||||
|
||||
except (TypeError, xmlrpclib.Fault):
|
||||
except Exception:
|
||||
image_data = self.placeholder(req)
|
||||
headers.append(('ETag', retag))
|
||||
headers.append(('Content-Length', len(image_data)))
|
||||
|
@ -1311,7 +1309,7 @@ class Binary(openerpweb.Controller):
|
|||
'filename': ufile.filename,
|
||||
'id': attachment_id
|
||||
}
|
||||
except xmlrpclib.Fault, e:
|
||||
except Exception:
|
||||
args = {'error':e.faultCode }
|
||||
return out % (simplejson.dumps(callback), simplejson.dumps(args))
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
.. _module:
|
||||
|
||||
Building an OpenERP Web module
|
||||
==============================
|
||||
|
||||
|
|
|
@ -19,7 +19,6 @@ import time
|
|||
import traceback
|
||||
import urlparse
|
||||
import uuid
|
||||
import xmlrpclib
|
||||
|
||||
import babel.core
|
||||
import simplejson
|
||||
|
@ -194,35 +193,18 @@ class JsonRequest(WebRequest):
|
|||
response['id'] = self.jsonrequest.get('id')
|
||||
response["result"] = method(self, **self.params)
|
||||
except session.AuthenticationError:
|
||||
se = serialize_exception(e)
|
||||
error = {
|
||||
'code': 100,
|
||||
'message': "OpenERP Session Invalid",
|
||||
'data': {
|
||||
'type': 'session_invalid',
|
||||
'debug': traceback.format_exc()
|
||||
}
|
||||
'data': se
|
||||
}
|
||||
except xmlrpclib.Fault, e:
|
||||
except Exception, e:
|
||||
se = serialize_exception(e)
|
||||
error = {
|
||||
'code': 200,
|
||||
'message': "OpenERP Server Error",
|
||||
'data': {
|
||||
'type': 'server_exception',
|
||||
'fault_code': e.faultCode,
|
||||
'debug': "Client %s\nServer %s" % (
|
||||
"".join(traceback.format_exception("", None, sys.exc_traceback)), e.faultString)
|
||||
}
|
||||
}
|
||||
except Exception:
|
||||
logging.getLogger(__name__ + '.JSONRequest.dispatch').exception\
|
||||
("An error occured while handling a json request")
|
||||
error = {
|
||||
'code': 300,
|
||||
'message': "OpenERP WebClient Error",
|
||||
'data': {
|
||||
'type': 'client_exception',
|
||||
'debug': "Client %s" % traceback.format_exc()
|
||||
}
|
||||
'data': se
|
||||
}
|
||||
if error:
|
||||
response["error"] = error
|
||||
|
@ -244,6 +226,36 @@ class JsonRequest(WebRequest):
|
|||
r = werkzeug.wrappers.Response(body, headers=[('Content-Type', mime), ('Content-Length', len(body))])
|
||||
return r
|
||||
|
||||
def serialize_exception(e):
|
||||
tmp = {
|
||||
"name": type(e).__module__ + "." + type(e).__name__ if type(e).__module__ else type(e).__name__,
|
||||
"debug": traceback.format_exc(),
|
||||
"message": u"%s" % e,
|
||||
"arguments": to_jsonable(e.args),
|
||||
}
|
||||
if isinstance(e, openerp.osv.osv.except_osv):
|
||||
tmp["exception_type"] = "except_osv"
|
||||
elif isinstance(e, openerp.exceptions.Warning):
|
||||
tmp["exception_type"] = "warning"
|
||||
elif isinstance(e, openerp.exceptions.AccessError):
|
||||
tmp["exception_type"] = "access_error"
|
||||
elif isinstance(e, openerp.exceptions.AccessDenied):
|
||||
tmp["exception_type"] = "access_denied"
|
||||
return tmp
|
||||
|
||||
def to_jsonable(o):
|
||||
if isinstance(o, str) or isinstance(o,unicode) or isinstance(o, int) or isinstance(o, long) \
|
||||
or isinstance(o, bool) or o is None or isinstance(o, float):
|
||||
return o
|
||||
if isinstance(o, list) or isinstance(o, tuple):
|
||||
return [to_jsonable(x) for x in o]
|
||||
if isinstance(o, dict):
|
||||
tmp = {}
|
||||
for k, v in o.items():
|
||||
tmp[u"%s" % k] = to_jsonable(v)
|
||||
return tmp
|
||||
return u"%s" % o
|
||||
|
||||
def jsonrequest(f):
|
||||
""" Decorator marking the decorated method as being a handler for a
|
||||
JSON-RPC request (the exact request path is specified via the
|
||||
|
@ -274,28 +286,14 @@ class HttpRequest(WebRequest):
|
|||
_logger.debug("%s --> %s.%s %r", self.httprequest.method, method.im_class.__name__, method.__name__, akw)
|
||||
try:
|
||||
r = method(self, **self.params)
|
||||
except xmlrpclib.Fault, e:
|
||||
r = werkzeug.exceptions.InternalServerError(cgi.escape(simplejson.dumps({
|
||||
except Exception:
|
||||
se = serialize_exception(e)
|
||||
error = {
|
||||
'code': 200,
|
||||
'message': "OpenERP Server Error",
|
||||
'data': {
|
||||
'type': 'server_exception',
|
||||
'fault_code': e.faultCode,
|
||||
'debug': "Server %s\nClient %s" % (
|
||||
e.faultString, traceback.format_exc())
|
||||
}
|
||||
})))
|
||||
except Exception:
|
||||
logging.getLogger(__name__ + '.HttpRequest.dispatch').exception(
|
||||
"An error occurred while handling a json request")
|
||||
r = werkzeug.exceptions.InternalServerError(cgi.escape(simplejson.dumps({
|
||||
'code': 300,
|
||||
'message': "OpenERP WebClient Error",
|
||||
'data': {
|
||||
'type': 'client_exception',
|
||||
'debug': "Client %s" % traceback.format_exc()
|
||||
}
|
||||
})))
|
||||
'data': se
|
||||
}
|
||||
r = werkzeug.exceptions.InternalServerError(cgi.escape(simplejson.dumps(error)))
|
||||
if self.debug or 1:
|
||||
if isinstance(r, (werkzeug.wrappers.BaseResponse, werkzeug.exceptions.HTTPException)):
|
||||
_logger.debug('<-- %s', r)
|
||||
|
|
|
@ -6,7 +6,6 @@ import logging
|
|||
import time
|
||||
import traceback
|
||||
import sys
|
||||
import xmlrpclib
|
||||
|
||||
import openerp
|
||||
|
||||
|
@ -85,23 +84,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"
|
||||
try:
|
||||
return openerp.netsvc.dispatch_rpc(service_name, method, args)
|
||||
except openerp.osv.osv.except_osv, e:
|
||||
raise xmlrpclib.Fault(code_string % (e.name, e.value), '')
|
||||
except openerp.exceptions.Warning, e:
|
||||
raise xmlrpclib.Fault(code_string % ("Warning", e), '')
|
||||
except openerp.exceptions.AccessError, e:
|
||||
raise xmlrpclib.Fault(code_string % ("AccessError", e), '')
|
||||
except openerp.exceptions.AccessDenied, e:
|
||||
raise xmlrpclib.Fault('AccessDenied', str(e))
|
||||
except openerp.exceptions.DeferredException, e:
|
||||
formatted_info = "".join(traceback.format_exception(*e.traceback))
|
||||
raise xmlrpclib.Fault(openerp.tools.ustr(e.message), 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)
|
||||
return openerp.netsvc.dispatch_rpc(service_name, method, args)
|
||||
|
||||
def proxy(self, service):
|
||||
return Service(self, service)
|
||||
|
|
|
@ -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
|
||||
*/
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -247,19 +247,17 @@ instance.web.CrashManager = instance.web.Class.extend({
|
|||
if (!this.active) {
|
||||
return;
|
||||
}
|
||||
// yes, exception handling is shitty
|
||||
if (error.code === 300 && error.data && error.data.type == "client_exception" && error.data.debug.match("SessionExpiredException")) {
|
||||
this.show_warning({type: "Session Expired", data: { fault_code: "Your OpenERP session expired. Please refresh the current web page." }});
|
||||
var handler = instance.web.crash_manager_registry.get_object(error.data.name, true);
|
||||
if (handler) {
|
||||
new (handler)(this, error).display();
|
||||
return;
|
||||
};
|
||||
if (error.data.name === "openerp.addons.web.session SessionExpiredException") {
|
||||
this.show_warning({type: "Session Expired", data: { message: "Your OpenERP session expired. Please refresh the current web page." }});
|
||||
return;
|
||||
}
|
||||
if (error.data.fault_code) {
|
||||
var split = ("" + error.data.fault_code).split('\n')[0].split(' -- ');
|
||||
if (split.length > 1) {
|
||||
error.type = split.shift();
|
||||
error.data.fault_code = error.data.fault_code.substr(error.type.length + 4);
|
||||
}
|
||||
}
|
||||
if (error.code === 200 && error.type) {
|
||||
if (error.data.exception_type === "except_osv" || error.data.exception_type === "warning"
|
||||
|| error.data.exception_type === "access_error") {
|
||||
this.show_warning(error);
|
||||
} else {
|
||||
this.show_error(error);
|
||||
|
@ -269,8 +267,11 @@ instance.web.CrashManager = instance.web.Class.extend({
|
|||
if (!this.active) {
|
||||
return;
|
||||
}
|
||||
if (error.data.exception_type === "except_osv") {
|
||||
error = _.extend({}, error, {data: _.extend({}, error.data, {message: error.data.arguments[0] + "\n\n" + error.data.arguments[1]})});
|
||||
}
|
||||
instance.web.dialog($('<div>' + QWeb.render('CrashManager.warning', {error: error}) + '</div>'), {
|
||||
title: "OpenERP " + _.str.capitalize(error.type),
|
||||
title: "OpenERP " + (_.str.capitalize(error.type) || "Warning"),
|
||||
buttons: [
|
||||
{text: _t("Ok"), click: function() { $(this).dialog("close"); }}
|
||||
]
|
||||
|
@ -303,6 +304,29 @@ instance.web.CrashManager = instance.web.Class.extend({
|
|||
},
|
||||
});
|
||||
|
||||
/**
|
||||
An interface to implement to handle exceptions. Register implementation in instance.web.crash_manager_registry.
|
||||
*/
|
||||
instance.web.ExceptionHandler = {
|
||||
/**
|
||||
@param parent The parent.
|
||||
@param error The error object as returned by the JSON-RPC implementation.
|
||||
*/
|
||||
init: function(parent, error) {},
|
||||
/**
|
||||
Called to inform to display the widget, if necessary. A typical way would be to implement
|
||||
this interface in a class extending instance.web.Dialog and simply display the dialog in this
|
||||
method.
|
||||
*/
|
||||
display: function() {},
|
||||
};
|
||||
|
||||
/**
|
||||
The registry to handle exceptions. It associate a fully qualified python exception name with a class implementing
|
||||
instance.web.ExceptionHandler.
|
||||
*/
|
||||
instance.web.crash_manager_registry = new instance.web.Registry();
|
||||
|
||||
instance.web.Loading = instance.web.Widget.extend({
|
||||
template: _t("Loading"),
|
||||
init: function(parent) {
|
||||
|
@ -481,6 +505,7 @@ instance.web.DatabaseManager = instance.web.Widget.extend({
|
|||
self.do_action("reload");
|
||||
},
|
||||
},
|
||||
_push_me: false,
|
||||
};
|
||||
self.do_action(client_action);
|
||||
});
|
||||
|
@ -649,7 +674,7 @@ instance.web.Login = instance.web.Widget.extend({
|
|||
}
|
||||
},
|
||||
on_db_failed: function (error, event) {
|
||||
if (error.data.fault_code === 'AccessDenied') {
|
||||
if (error.data.name === 'openerp.exceptions.AccessDenied') {
|
||||
event.preventDefault();
|
||||
}
|
||||
},
|
||||
|
@ -868,7 +893,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 +1420,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');
|
||||
|
|
|
@ -991,7 +991,7 @@ instance.web.JsonRPC = instance.web.Class.extend(instance.web.PropertiesMixin, {
|
|||
self.trigger('response', response);
|
||||
if (!response.error) {
|
||||
deferred.resolve(response["result"], textStatus, jqXHR);
|
||||
} else if (response.error.data.type === "session_invalid") {
|
||||
} else if (response.error.code === 100) {
|
||||
self.uid = false;
|
||||
} else {
|
||||
deferred.reject(response.error, $.Event());
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -72,7 +72,7 @@ openerp.test_support = {
|
|||
}
|
||||
return;
|
||||
}
|
||||
fn(e.data.fault_code);
|
||||
fn(e.data.name);
|
||||
})
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -40,7 +40,7 @@
|
|||
<td>
|
||||
<p>
|
||||
<t t-js="d">
|
||||
var message = d.message ? d.message : d.error.data.fault_code;
|
||||
var message = d.message ? d.message : d.error.data.message;
|
||||
d.html_error = context.engine.tools.html_escape(message)
|
||||
.replace(/\n/g, '<br/>');
|
||||
</t>
|
||||
|
@ -1414,8 +1414,6 @@
|
|||
this.removeAttr('t-if');
|
||||
</t>
|
||||
|
||||
<t t-jquery="tfoot > tr:last-child" t-operation="replace"/>
|
||||
|
||||
<t t-jquery="td.oe-actions">
|
||||
this.removeAttr('t-if');
|
||||
</t>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -426,7 +426,7 @@ instance.web_kanban.KanbanView = instance.web.View.extend({
|
|||
new_group.do_save_sequences();
|
||||
}).fail(function(error, evt) {
|
||||
evt.preventDefault();
|
||||
alert(_t("An error has occured while moving the record to this group: ") + data.fault_code);
|
||||
alert(_t("An error has occured while moving the record to this group: ") + data.message);
|
||||
self.do_reload(); // TODO: use draggable + sortable in order to cancel the dragging when the rcp fails
|
||||
});
|
||||
}
|
||||
|
@ -646,7 +646,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;
|
||||
|
|
Loading…
Reference in New Issue