diff --git a/addons/base/__openerp__.py b/addons/base/__openerp__.py index cfe8850ab03..71b078f8d98 100644 --- a/addons/base/__openerp__.py +++ b/addons/base/__openerp__.py @@ -5,6 +5,7 @@ 'active': True, 'js' : [ "static/lib/datejs/date-en-US.js", + "static/lib/datejs/strftime.js", "static/lib/jquery/jquery-1.6.2.js", "static/lib/jquery.form/jquery.form.js", "static/lib/jquery.validate/jquery.validate.js", @@ -22,6 +23,7 @@ "static/lib/underscore/underscore.string.js", "static/src/js/boot.js", "static/src/js/core.js", + "static/src/js/dates.js", "static/src/js/formats.js", "static/src/js/chrome.js", "static/src/js/views.js", diff --git a/addons/base/controllers/main.py b/addons/base/controllers/main.py index 3caa07c6197..d5eaafaecee 100644 --- a/addons/base/controllers/main.py +++ b/addons/base/controllers/main.py @@ -67,7 +67,6 @@ def manifest_glob(addons, key): files = [] for addon in addons: globlist = openerpweb.addons_manifest.get(addon, {}).get(key, []) - print globlist for pattern in globlist: for path in glob.glob(os.path.join(openerpweb.path_addons, addon, pattern)): files.append(path[len(openerpweb.path_addons):]) @@ -79,35 +78,36 @@ def concat_files(file_list): concat: concatenation of file content timestamp: max(os.path.getmtime of file_list) """ - root = openerpweb.path_root files_content = [] files_timestamp = 0 for i in file_list: - fname = os.path.join(root, i) + fname = os.path.join(openerpweb.path_addons, i[1:]) ftime = os.path.getmtime(fname) if ftime > files_timestamp: files_timestamp = ftime - files_content = open(fname).read() + files_content.append(open(fname).read()) files_concat = "".join(files_content) - return files_concat + return (files_concat,files_timestamp) home_template = textwrap.dedent(""" OpenERP - %(javascript)s - %(css)s + %(javascript)s + @@ -125,31 +125,31 @@ class WebClient(openerpweb.Controller): @openerpweb.httprequest def css(self, req, mods='base'): - cherrypy.response.headers['Content-Type'] = 'text/css' + req.httpresponse.headers['Content-Type'] = 'text/css' files = manifest_glob(mods.split(','), 'css') - concat = concat_files(files)[0] + content,timestamp = concat_files(files) # TODO request set the Date of last modif and Etag - return concat + return content @openerpweb.httprequest def js(self, req, mods='base'): - cherrypy.response.headers['Content-Type'] = 'application/javascript' + req.httpresponse.headers['Content-Type'] = 'application/javascript' files = manifest_glob(mods.split(','), 'js') - concat = concat_files(files)[0] + content,timestamp = concat_files(files) # TODO request set the Date of last modif and Etag - return concat + return content @openerpweb.httprequest def home(self, req, s_action=None): # script tags jslist = ['/base/webclient/js'] - if 1: # debug == 1 + if req.debug: jslist = manifest_glob(['base'], 'js') js = "\n ".join([''%i for i in jslist]) # css tags csslist = ['/base/webclient/css'] - if 1: # debug == 1 + if req.debug: csslist = manifest_glob(['base'], 'css') css = "\n ".join([''%i for i in csslist]) r = home_template % { @@ -157,7 +157,7 @@ class WebClient(openerpweb.Controller): 'css': css } return r - + @openerpweb.jsonrequest def translations(self, req, mods, lang): lang_model = req.session.model('res.lang') @@ -193,7 +193,6 @@ class WebClient(openerpweb.Controller): transl["messages"].append({'id': x.id, 'string': x.string}) return {"modules": transs, "lang_parameters": lang_obj} - class Database(openerpweb.Controller): _cp_path = "/base/database" @@ -249,16 +248,16 @@ class Database(openerpweb.Controller): try: db_dump = base64.decodestring( req.session.proxy("db").dump(backup_pwd, backup_db)) - cherrypy.response.headers['Content-Type'] = "application/octet-stream; charset=binary" - cherrypy.response.headers['Content-Disposition'] = 'attachment; filename="' + backup_db + '.dump"' - cherrypy.response.cookie['fileToken'] = token - cherrypy.response.cookie['fileToken']['path'] = '/' + req.httpresponse.headers['Content-Type'] = "application/octet-stream; charset=binary" + req.httpresponse.headers['Content-Disposition'] = 'attachment; filename="' + backup_db + '.dump"' + req.httpresponse.cookie['fileToken'] = token + req.httpresponse.cookie['fileToken']['path'] = '/' return db_dump except xmlrpclib.Fault, e: if e.faultCode and e.faultCode.split(':')[0] == 'AccessDenied': return 'Backup Database|' + e.faultCode return 'Backup Database|Could not generate database backup' - + @openerpweb.httprequest def restore(self, req, db_file, restore_pwd, new_db): try: @@ -267,9 +266,7 @@ class Database(openerpweb.Controller): return '' except xmlrpclib.Fault, e: if e.faultCode and e.faultCode.split(':')[0] == 'AccessDenied': - raise cherrypy.HTTPError(403) - - raise cherrypy.HTTPError() + raise Exception("AccessDenied") @openerpweb.jsonrequest def change_password(self, req, fields): @@ -386,10 +383,10 @@ class Session(openerpweb.Controller): :return: A key identifying the saved action. :rtype: integer """ - saved_actions = cherrypy.session.get('saved_actions') + saved_actions = req.httpsession.get('saved_actions') if not saved_actions: saved_actions = {"next":0, "actions":{}} - cherrypy.session['saved_actions'] = saved_actions + req.httpsession['saved_actions'] = saved_actions # we don't allow more than 10 stored actions if len(saved_actions["actions"]) >= 10: del saved_actions["actions"][min(saved_actions["actions"].keys())] @@ -409,7 +406,7 @@ class Session(openerpweb.Controller): :return: The saved action or None. :rtype: anything """ - saved_actions = cherrypy.session.get('saved_actions') + saved_actions = req.httpsession.get('saved_actions') if not saved_actions: return None return saved_actions["actions"].get(key) @@ -439,18 +436,16 @@ def clean_action(action, session, context=None): return action # values come from the server, we can just eval them if isinstance(action.get('context'), basestring): - action['context'] = eval( - action['context'], - session.evaluation_context(context=context)) or {} + localvars = session.evaluation_context(context=context) + action['context'] = eval( action['context'], localvars ) or {} if isinstance(action.get('domain'), basestring): - action['domain'] = eval( - action['domain'], - session.evaluation_context( - action.get('context', {}))) or [] + localvars = session.evaluation_context( action.get('context', {})) + action['domain'] = eval( action['domain'], localvars ) or [] return fix_view_modes(action) +# I think generate_views,fix_view_modes should go into js ActionManager def generate_views(action): """ While the server generates a sequence called "views" computing dependencies @@ -947,10 +942,10 @@ 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' - Model = request.session.model(model) - context = request.session.eval_context(request.context) + def image(self, req, model, id, field, **kw): + req.httpresponse.headers['Content-Type'] = 'image/png' + Model = req.session.model(model) + context = req.session.eval_context(req.context) try: if not id: res = Model.default_get([field], context).get(field, '') @@ -963,26 +958,26 @@ class Binary(openerpweb.Controller): return open(os.path.join(openerpweb.path_addons, 'base', 'static', 'src', 'img', 'placeholder.png'), 'rb').read() @openerpweb.httprequest - def saveas(self, request, session_id, model, id, field, fieldname, **kw): - Model = request.session.model(model) - context = request.session.eval_context(request.context) + def saveas(self, req, model, id, field, fieldname, **kw): + Model = req.session.model(model) + context = req.session.eval_context(req.context) res = Model.read([int(id)], [field, fieldname], context)[0] filecontent = res.get(field, '') if not filecontent: raise cherrypy.NotFound else: - cherrypy.response.headers['Content-Type'] = 'application/octet-stream' + req.httpresponse.headers['Content-Type'] = 'application/octet-stream' filename = '%s_%s' % (model.replace('.', '_'), id) if fieldname: filename = res.get(fieldname, '') or filename - cherrypy.response.headers['Content-Disposition'] = 'attachment; filename=' + filename + req.httpresponse.headers['Content-Disposition'] = 'attachment; filename=' + filename return base64.decodestring(filecontent) @openerpweb.httprequest - def upload(self, request, session_id, callback, ufile=None): + def upload(self, req, callback, ufile=None): cherrypy.response.timeout = 500 headers = {} - for key, val in cherrypy.request.headers.iteritems(): + for key, val in req.httprequest.headers.iteritems(): headers[key.lower()] = val size = int(headers.get('content-length', 0)) # TODO: might be useful to have a configuration flag for max-length file uploads @@ -1006,10 +1001,10 @@ class Binary(openerpweb.Controller): return out % (simplejson.dumps(callback), simplejson.dumps(args)) @openerpweb.httprequest - def upload_attachment(self, request, session_id, callback, model, id, ufile=None): + def upload_attachment(self, req, callback, model, id, ufile=None): cherrypy.response.timeout = 500 - context = request.session.eval_context(request.context) - Model = request.session.model('ir.attachment') + context = req.session.eval_context(req.context) + Model = req.session.model('ir.attachment') try: out = """ + + + +

OpenERP web_rpc examples

+

Example 1: Display a list of defined ir.model

+

Code:

+
+<script type="text/javascript" src="/base/webclient/js?mods=web_rpc"></script> 
+<script type="text/javascript">
+
+    var c = openerp.init(); // get a new webclient
+    c._modules_loaded = true; // Hack to prevent loading of additional modules
+
+    var s = new c.base.Session(); // setup a Session
+    s.session_login("web-trunk", "admin", "admin", function() {
+
+            var ds = new c.base.DataSetSearch(s, "ir.model"); // DataSetSearch used to search, read
+            ds.read_slice(['name','model'], {}, function(users){
+                for(var i in users) { 
+                    $("#ex1res").append("<li>" + users[i].model + " (" + users[i].name + ") </li>")
+                }
+            });
+        }
+    );
+
</script> +
+

Div for output:

+
+   +
+

Help me to complete this example page on launchpad

+ + + diff --git a/babel.cfg b/babel.cfg index c8899df863f..02820af13d6 100644 --- a/babel.cfg +++ b/babel.cfg @@ -1,6 +1,8 @@ [extractors] qweb = npybabel:extract_qweb +xmljs = npybabel:extract_xmljs [javascript: static/src/js/**.js] [qweb: static/src/xml/**.xml] +[xmljs: static/src/xml/**.xml] diff --git a/doc/source/addons.rst b/doc/source/addons.rst index 7261e57ebd3..8bff7aae60d 100644 --- a/doc/source/addons.rst +++ b/doc/source/addons.rst @@ -195,6 +195,13 @@ The new class can then be used in the following manner: After these two lines have executed (and any promise returned by ``appendTo`` has been resolved if needed), the widget is ready to be used. +.. note:: the insertion methods will start the widget themselves, and will + return the result of :js:func:`~openerp.base.Widget.start()`. + + If for some reason you do not want to call these methods, you will + have to first call :js:func:`~openerp.base.Widget.render()` on the + widget, then insert it into your DOM and start it. + If the widget is not needed anymore (because it's transient), simply terminate it: diff --git a/npybabel.py b/npybabel.py index 3d9deaec676..0a3c75025c9 100755 --- a/npybabel.py +++ b/npybabel.py @@ -5,15 +5,30 @@ import sys from pkg_resources import load_entry_point import re import json +import xml.etree.ElementTree as elt if __name__ == '__main__': sys.exit( load_entry_point('Babel==0.9.6', 'console_scripts', 'pybabel')() ) -QWEB_EXPR = re.compile(r"""(?:\< *t\-tr *\>(.*?)\< *\/t\-tr *\>)|(?:\_t *\( *((?:"(?:[^"\\]|\\.)*")|(?:'(?:[^'\\]|\\.)*')) *\))""") -XML_GROUP = 1 -JS_GROUP = 2 +XMLJS_EXPR = re.compile(r"""(?:\_t *\( *((?:"(?:[^"\\]|\\.)*")|(?:'(?:[^'\\]|\\.)*')) *\))""") + +def extract_xmljs(fileobj, keywords, comment_tags, options): + content = fileobj.read() + found = XMLJS_EXPR.finditer(content) + result = [] + index = 0 + line_nbr = 0 + for f in found: + mes = f.group(1) + mes = json.loads(mes) + while index < f.start(): + if content[index] == "\n": + line_nbr += 1 + index += 1 + result.append((line_nbr, None, mes, "")) + return result def extract_qweb(fileobj, keywords, comment_tags, options): """Extract messages from XXX files. @@ -28,19 +43,23 @@ def extract_qweb(fileobj, keywords, comment_tags, options): tuples :rtype: ``iterator`` """ - content = fileobj.read() - found = QWEB_EXPR.finditer(content) result = [] - index = 0 - line_nbr = 0 - for f in found: - group = XML_GROUP if f.group(XML_GROUP) else JS_GROUP - mes = f.group(group) - if group == JS_GROUP: - mes = json.loads(mes) - while index < f.start(): - if content[index] == "\n": - line_nbr += 1 - index += 1 - result.append((line_nbr, None, mes, "")) + def handle_text(str): + str = (str or "").strip() + if not str: + return + result.append((0, None, str, "")) + + def iter_elements(current_element): + for el in current_element: + if "t-js" not in el.attrib and \ + not ("t-jquery" in el.attrib and "t-operation" not in el.attrib) and \ + not ("t-trad" in el.attrib and el.attrib["t-trad"].strip() == "off"): + handle_text(el.text) + iter_elements(el) + handle_text(el.tail) + + tree = elt.parse(fileobj) + iter_elements(tree.getroot()) + return result diff --git a/openerpweb/dates.py b/openerpweb/dates.py index be3ffdf5f45..caa7f83c84a 100644 --- a/openerpweb/dates.py +++ b/openerpweb/dates.py @@ -28,7 +28,7 @@ DEFAULT_SERVER_DATETIME_FORMAT = "%s %s" % ( DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_TIME_FORMAT) -def parse_datetime(str): +def str_to_datetime(str): """ Converts a string to a datetime object using OpenERP's datetime string format (exemple: '2011-12-01 15:12:35'). @@ -40,7 +40,7 @@ def parse_datetime(str): return str return datetime.datetime.strptime(str, DEFAULT_SERVER_DATETIME_FORMAT) -def parse_date(str): +def str_to_date(str): """ Converts a string to a date object using OpenERP's date string format (exemple: '2011-12-01'). @@ -49,7 +49,7 @@ def parse_date(str): return str return datetime.datetime.strptime(str, DEFAULT_SERVER_DATE_FORMAT).date() -def parse_time(str): +def str_to_time(str): """ Converts a string to a time object using OpenERP's time string format (exemple: '15:12:35'). @@ -58,7 +58,7 @@ def parse_time(str): return str return datetime.datetime.strptime(str, DEFAULT_SERVER_TIME_FORMAT).time() -def format_datetime(obj): +def datetime_to_str(obj): """ Converts a datetime object to a string using OpenERP's datetime string format (exemple: '2011-12-01 15:12:35'). @@ -69,7 +69,7 @@ def format_datetime(obj): return False return obj.strftime(DEFAULT_SERVER_DATETIME_FORMAT) -def format_date(obj): +def date_to_str(obj): """ Converts a date object to a string using OpenERP's date string format (exemple: '2011-12-01'). @@ -78,7 +78,7 @@ def format_date(obj): return False return obj.strftime(DEFAULT_SERVER_DATE_FORMAT) -def format_time(obj): +def time_to_str(obj): """ Converts a time object to a string using OpenERP's time string format (exemple: '15:12:35'). diff --git a/openerpweb/openerpweb.py b/openerpweb/openerpweb.py index ad012a0806c..a48162138dc 100644 --- a/openerpweb/openerpweb.py +++ b/openerpweb/openerpweb.py @@ -233,7 +233,27 @@ class OpenERPSession(object): #---------------------------------------------------------- # OpenERP Web RequestHandler #---------------------------------------------------------- -class JsonRequest(object): +class CherryPyRequest(object): + """ CherryPy request handling + """ + def init(self,params): + self.params = params + # Move cherrypy thread local objects to attributes + self.applicationsession = applicationsession + self.httprequest = cherrypy.request + self.httpresponse = cherrypy.response + self.httpsession = cherrypy.session + self.httpsession_id = "cookieid" + # OpenERP session setup + self.session_id = self.params.pop("session_id", None) or uuid.uuid4().hex + host = cherrypy.config['openerp.server.host'] + port = cherrypy.config['openerp.server.port'] + self.session = self.httpsession.setdefault(self.session_id, OpenERPSession(host, port)) + # Request attributes + self.context = self.params.pop('context', None) + self.debug = self.params.pop('debug',False) != False + +class JsonRequest(CherryPyRequest): """ JSON-RPC2 over HTTP. Sucessful request:: @@ -267,48 +287,28 @@ class JsonRequest(object): """ - def parse(self, request): - self.request = request - self.params = request.get("params", {}) - self.applicationsession = applicationsession - self.httprequest = cherrypy.request - self.httpresponse = cherrypy.response - self.httpsession = cherrypy.session - self.httpsession_id = "cookieid" - self.httpsession = cherrypy.session - self.session_id = self.params.pop("session_id", None) or uuid.uuid4().hex - host = cherrypy.config['openerp.server.host'] - port = cherrypy.config['openerp.server.port'] - self.session = self.httpsession.setdefault(self.session_id, OpenERPSession(host, port)) - self.context = self.params.pop('context', None) - return self.params - def dispatch(self, controller, method, requestf=None, request=None): """ Calls the method asked for by the JSON-RPC2 request :param controller: the instance of the controller which received the request - :type controller: type :param method: the method which received the request - :type method: callable :param requestf: a file-like object containing an encoded JSON-RPC2 request - :type requestf: bytes> - :param request: an encoded JSON-RPC2 request - :type request: bytes + :param request: a JSON-RPC2 request - :returns: a string-encoded JSON-RPC2 reply - :rtype: bytes + :returns: an utf8 encoded JSON-RPC2 reply """ - # Read POST content or POST Form Data named "request" - if requestf: - request = simplejson.load(requestf, object_hook=nonliterals.non_literal_decoder) - else: - request = simplejson.loads(request, object_hook=nonliterals.non_literal_decoder) - - response = {"jsonrpc": "2.0", "id": request.get('id')} + response = {"jsonrpc": "2.0" } + error = None try: - print "--> %s.%s %s" % (controller.__class__.__name__, method.__name__, request) - error = None - self.parse(request) + # Read POST content or POST Form Data named "request" + if requestf: + self.jsonrequest = simplejson.load(requestf, object_hook=nonliterals.non_literal_decoder) + else: + self.jsonrequest = simplejson.loads(request, object_hook=nonliterals.non_literal_decoder) + self.init(self.jsonrequest.get("params", {})) + if self.debug or 1: + print "--> %s.%s %s" % (controller.__class__.__name__, method.__name__, self.jsonrequest) + response['id'] = self.jsonrequest.get('id') response["result"] = method(controller, self, **self.params) except OpenERPUnboundException: error = { @@ -344,8 +344,9 @@ class JsonRequest(object): if error: response["error"] = error - print "<--", response - print + if self.debug or 1: + print "<--", response + print content = simplejson.dumps(response, cls=nonliterals.NonLiteralEncoder) cherrypy.response.headers['Content-Type'] = 'application/json' @@ -357,40 +358,32 @@ def jsonrequest(f): @functools.wraps(f) def json_handler(controller): return JsonRequest().dispatch(controller, f, requestf=cherrypy.request.body) - return json_handler -class HttpRequest(object): +class HttpRequest(CherryPyRequest): """ Regular GET/POST request """ - def dispatch(self, controller, f, request, **kw): - self.request = request - self.applicationsession = applicationsession - self.httpsession_id = "cookieid" - self.httpsession = cherrypy.session - self.context = kw.get('context', {}) - host = cherrypy.config['openerp.server.host'] - port = cherrypy.config['openerp.server.port'] - self.session = self.httpsession.setdefault(kw.pop('session_id', None), OpenERPSession(host, port)) - self.result = "" - if request.method == 'GET': - print "GET --> %s.%s %s %r" % (controller.__class__.__name__, f.__name__, request, kw) - else: - akw = dict([(key, kw[key] if isinstance(kw[key], basestring) else type(kw[key])) for key in kw.keys()]) - print "POST --> %s.%s %s %r" % (controller.__class__.__name__, f.__name__, request, akw) - r = f(controller, self, **kw) - if isinstance(r, str): - print "<--", len(r), 'bytes' - else: - print "<--", len(r), 'characters' - print + def dispatch(self, controller, method, **kw): + self.init(kw) + akw = {} + for key in kw.keys(): + if isinstance(kw[key], basestring) and len(kw[key]) < 1024: + akw[key] = kw[key] + else: + akw[key] = type(kw[key]) + if self.debug or 1: + print "%s --> %s.%s %r" % (self.httprequest.method, controller.__class__.__name__, method.__name__, akw) + r = method(controller, self, **kw) + if self.debug or 1: + print "<--", 'size:', len(r) + print return r def httprequest(f): # check cleaner wrapping: # functools.wraps(f)(lambda x: JsonRequest().dispatch(x, f)) - def http_handler(self,*l, **kw): - return HttpRequest().dispatch(self, f, cherrypy.request, **kw) + def http_handler(controller,*l, **kw): + return HttpRequest().dispatch(controller, f, **kw) http_handler.exposed = 1 return http_handler