diff --git a/addons/base/__openerp__.py b/addons/base/__openerp__.py index da2b345d217..c2484c9ed4c 100644 --- a/addons/base/__openerp__.py +++ b/addons/base/__openerp__.py @@ -3,4 +3,37 @@ "version" : "2.0", "depends" : [], 'active': True, + 'js' : [ + "static/lib/LABjs/LAB.js", + "static/lib/datejs/date-en-US.js", + "static/lib/jquery/jquery-1.5.2.js", + "static/lib/jquery.ba-bbq/jquery.ba-bbq.js", + "static/lib/jquery.contextmenu/jquery.contextmenu.r2.packed.js", + "static/lib/jquery.superfish/js/hoverIntent.js", + "static/lib/jquery.superfish/js/superfish.js", + "static/lib/jquery.ui/js/jquery-ui-1.8.9.custom.min.js", + "static/lib/jquery.ui/js/jquery-ui-timepicker-addon.js", + "static/lib/jquery.ui.notify/js/jquery.notify.js", + "static/lib/json/json2.js", + "static/lib/qweb/qweb2.js", + "static/lib/underscore/underscore.js", + "static/lib/underscore/underscore.string.js", + "static/src/js/base.js", + "static/src/js/chrome.js", + "static/src/js/controller.js", + "static/src/js/views.js", + "static/src/js/data.js", + "static/src/js/dates.js", + "static/src/js/form.js", + "static/src/js/list.js", + "static/src/js/list-editable.js", + "static/src/js/search.js", + "static/src/js/view_tree.js", + ], + 'css' : [ + "static/lib/jquery.superfish/css/superfish.css", + "static/lib/jquery.ui/css/smoothness/jquery-ui-1.8.9.custom.css", + "static/lib/jquery.ui.notify/css/ui.notify.css", + "static/src/css/base.css", + ], } diff --git a/addons/base/controllers/main.py b/addons/base/controllers/main.py index 6bc323901e0..b572fb6b67c 100644 --- a/addons/base/controllers/main.py +++ b/addons/base/controllers/main.py @@ -54,6 +54,99 @@ class Xml2Json: # OpenERP Web base Controllers #---------------------------------------------------------- +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):]) + return files + +def concat_files(file_list): + """ Concatenate file content + return (concat,timestamp) + 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) + ftime = os.path.getmtime(fname) + if ftime > files_timestamp: + files_timestamp = ftime + files_content = open(fname).read() + files_concat = "".join(files_content) + return files_concat + +class WebClient(openerpweb.Controller): + _cp_path = "/base/webclient" + + @openerpweb.jsonrequest + def csslist(self, req, mods='base'): + return manifest_glob(mods.split(','), 'css') + + @openerpweb.jsonrequest + def jslist(self, req, mods='base'): + return manifest_glob(mods.split(','), 'js') + + @openerpweb.httprequest + def css(self, req, mods='base'): + cherrypy.response.headers['Content-Type'] = 'text/css' + files = manifest_glob(mods.split(','), 'css') + concat = concat_files(files)[0] + # TODO request set the Date of last modif and Etag + return concat + + @openerpweb.httprequest + def js(self, req, mods='base'): + cherrypy.response.headers['Content-Type'] = 'application/javascript' + files = manifest_glob(mods.split(','), 'js') + concat = concat_files(files)[0] + # TODO request set the Date of last modif and Etag + return concat + + @openerpweb.httprequest + def home(self, req): + template =""" + + + + OpenERP + %s + + + %s + + + + + """.replace('\n'+' '*8,'\n') + + # script tags + jslist = ['/base/webclient/js'] + if 1: # debug == 1 + jslist = manifest_glob(['base'], 'js') + js = "\n ".join([''%i for i in jslist]) + + # css tags + csslist = ['/base/webclient/css'] + if 1: # debug == 1 + csslist = manifest_glob(['base'], 'css') + css = "\n ".join([''%i for i in csslist]) + r = template % (js, css) + return r + class Database(openerpweb.Controller): _cp_path = "/base/database" @@ -71,37 +164,6 @@ class Database(openerpweb.Controller): class Session(openerpweb.Controller): _cp_path = "/base/session" - def manifest_glob(self, addons, key): - files = [] - for addon in addons: - globlist = openerpweb.addons_manifest.get(addon, {}).get(key, []) - - files.extend([ - resource_path[len(openerpweb.path_addons):] - for pattern in globlist - for resource_path in glob.glob(os.path.join( - openerpweb.path_addons, addon, pattern)) - ]) - return files - - def concat_files(self, file_list): - """ Concatenate file content - return (concat,timestamp) - 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) - ftime = os.path.getmtime(fname) - if ftime > files_timestamp: - files_timestamp = ftime - files_content = open(fname).read() - files_concat = "".join(files_content) - return files_concat - @openerpweb.jsonrequest def login(self, req, db, login, password): req.session.login(db, login, password) @@ -122,28 +184,6 @@ class Session(openerpweb.Controller): for name, manifest in openerpweb.addons_manifest.iteritems() if manifest.get('active', True)]} - @openerpweb.jsonrequest - def csslist(self, req, mods='base'): - return {'files': self.manifest_glob(mods.split(','), 'css')} - - @openerpweb.jsonrequest - def jslist(self, req, mods='base'): - return {'files': self.manifest_glob(mods.split(','), 'js')} - - def css(self, req, mods='base'): - files = self.manifest_glob(mods.split(','), 'css') - concat = self.concat_files(files)[0] - # TODO request set the Date of last modif and Etag - return concat - css.exposed = True - - def js(self, req, mods='base'): - files = self.manifest_glob(mods.split(','), 'js') - concat = self.concat_files(files)[0] - # TODO request set the Date of last modif and Etag - return concat - js.exposed = True - @openerpweb.jsonrequest def eval_domain_and_context(self, req, contexts, domains, group_by_seq=None): diff --git a/addons/base/static/src/base.html b/addons/base/static/src/base.html deleted file mode 100644 index 0c615ed8903..00000000000 --- a/addons/base/static/src/base.html +++ /dev/null @@ -1,56 +0,0 @@ - - - - - OpenERP - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/addons/base/static/src/js/chrome.js b/addons/base/static/src/js/chrome.js index 75f8a015fa8..590955fffab 100644 --- a/addons/base/static/src/js/chrome.js +++ b/addons/base/static/src/js/chrome.js @@ -137,57 +137,109 @@ openerp.base.Registry = openerp.base.Class.extend( /** @lends openerp.base.Regis }); /** - * Generates an inherited class that replaces all the methods by null methods (methods - * that does nothing and always return undefined). - * - * @param {Class} claz - * @param {dict} add Additional functions to override. - * @return {Class} + * OpenERP session aware controller + * a controller takes an already existing dom element and manage it */ -openerp.base.generate_null_object_class = function(claz, add) { - var newer = {}; - var copy_proto = function(prototype) { - for (var name in prototype) { - if(typeof prototype[name] == "function") { - newer[name] = function() {}; - } - } - if (prototype.prototype) - copy_proto(prototype.prototype); - }; - copy_proto(claz.prototype); - newer.init = openerp.base.BasicController.prototype.init; - var tmpclass = claz.extend(newer); - return tmpclass.extend(add || {}); -}; - -openerp.base.Notification = openerp.base.BasicController.extend({ +openerp.base.Controller = openerp.base.Controller.extend( /** @lends openerp.base.Controller# */{ init: function(parent, element_id) { this._super(parent, element_id); - this.$element.notify({ - speed: 500, - expires: 1500 - }); + if(this.controller_parent && this.controller_parent.session) { + this.session = this.controller_parent.session; + } }, - notify: function(title, text) { - this.$element.notify('create', { - title: title, - text: text - }); + /** + * Performs a JSON-RPC call + * + * @param {String} url endpoint url + * @param {Object} data RPC parameters + * @param {Function} success RPC call success callback + * @param {Function} error RPC call error callback + * @returns {jQuery.Deferred} deferred object for the RPC call + */ + rpc: function(url, data, success, error) { + return this.session.rpc(url, data, success, error); }, - warn: function(title, text) { - this.$element.notify('create', 'oe_notification_alert', { - title: title, - text: text - }); + do_action: function(action, on_finished) { + return this.parent.do_action(action, on_finished); } }); -// Session should be a Class not Controller -openerp.base.Session = openerp.base.BasicController.extend( /** @lends openerp.base.Session# */{ +/** + * OpenERP session aware widget + * A widget is a controller that doesnt take an element_id + * it render its own html render() that you should insert into the dom + * and bind it at start() + */ +openerp.base.BaseWidget = openerp.base.Controller.extend({ + /** + * The name of the QWeb template that will be used for rendering. Must be + * redefined in subclasses or the render() method can not be used. + * + * @type string + */ + template: null, + /** + * The prefix used to generate an id automatically. Should be redefined in + * subclasses. If it is not defined, a default identifier will be used. + * + * @type string + */ + identifier_prefix: 'generic-identifier', + /** + * Base class for widgets. Handle rendering (based on a QWeb template), + * identifier generation, parenting and destruction of the widget. + * Also initialize the identifier. + * + * @constructs + * @params {openerp.base.search.BaseWidget} parent The parent widget. + */ + init: function (parent) { + this._super(parent); + this.make_id(this.identifier_prefix); + }, + /** + * Sets and returns a globally unique identifier for the widget. + * + * If a prefix is appended, the identifier will be appended to it. + * + * @params sections prefix sections, empty/falsy sections will be removed + */ + make_id: function () { + this.element_id = _.uniqueId(_.toArray(arguments).join('_')); + return this.element_id; + }, + /** + * Render the widget. This.template must be defined. + * The content of the current object is passed as context to the template. + * + * @param {object} additional Additional context arguments to pass to the template. + */ + render: function (additional) { + return QWeb.render(this.template, _.extend({}, this, additional != null ? additional : {})); + }, + /** + * "Starts" the widgets. Called at the end of the rendering, this allows + * to get a jQuery object referring to the DOM ($element attribute). + */ + start: function () { + this._super(); + var tmp = document.getElementById(this.element_id); + this.$element = tmp ? $(tmp) : null; + }, + /** + * "Stops" the widgets. Called when the view destroys itself, this + * lets the widgets clean up after themselves. + */ + stop: function () { + if(this.$element != null) { + this.$element.remove(); + } + } +}); + +openerp.base.Session = openerp.base.Controller.extend( /** @lends openerp.base.Session# */{ /** * @constructs - * @extends openerp.base.BasicController * @param element_id to use for exception reporting * @param server * @param port @@ -401,13 +453,13 @@ openerp.base.Session = openerp.base.BasicController.extend( /** @lends openerp.b this.rpc('/base/session/modules', {}, function(result) { self.module_list = result['modules']; var modules = self.module_list.join(','); - self.rpc('/base/session/csslist', {mods: modules}, self.do_load_css); - self.rpc('/base/session/jslist', {"mods": modules}, self.debug ? self.do_load_modules_debug : self.do_load_modules_prod); + self.rpc('/base/webclient/csslist', {mods: modules}, self.do_load_css); + self.rpc('/base/webclient/jslist', {"mods": modules}, self.debug ? self.do_load_modules_debug : self.do_load_modules_prod); openerp._modules_loaded = true; }); }, do_load_css: function (result) { - _.each(result.files, function (file) { + _.each(result, function (file) { $('head').append($('', { 'href': file, 'rel': 'stylesheet', @@ -417,7 +469,7 @@ openerp.base.Session = openerp.base.BasicController.extend( /** @lends openerp.b }, do_load_modules_debug: function(result) { $LAB.setOptions({AlwaysPreserveOrder: true}) - .script(result.files) + .script(result) .wait(this.on_modules_loaded); }, do_load_modules_prod: function() { @@ -441,109 +493,25 @@ openerp.base.Session = openerp.base.BasicController.extend( /** @lends openerp.b } }); -/** - * OpenERP session aware controller - * a controller takes an already existing dom element and manage it - */ -openerp.base.Controller = openerp.base.BasicController.extend( /** @lends openerp.base.Controller# */{ - /** - * @constructs - * @extends openerp.base.BasicController - */ +openerp.base.Notification = openerp.base.Controller.extend({ init: function(parent, element_id) { this._super(parent, element_id); - if(this.parent && this.parent.session) { - this.session = this.parent.session; - } + this.$element.notify({ + speed: 500, + expires: 1500 + }); }, - /** - * Performs a JSON-RPC call - * - * @param {String} url endpoint url - * @param {Object} data RPC parameters - * @param {Function} success RPC call success callback - * @param {Function} error RPC call error callback - * @returns {jQuery.Deferred} deferred object for the RPC call - */ - rpc: function(url, data, success, error) { - return this.session.rpc(url, data, success, error); + notify: function(title, text) { + this.$element.notify('create', { + title: title, + text: text + }); }, - do_action: function(action, on_finished) { - return this.parent.do_action(action, on_finished); - } -}); - -/** - * OpenERP session aware widget - * A widget is a controller that doesnt take an element_id - * it render its own html render() that you should insert into the dom - * and bind it a start() - */ -openerp.base.BaseWidget = openerp.base.Controller.extend({ - /** - * The name of the QWeb template that will be used for rendering. Must be - * redefined in subclasses or the render() method can not be used. - * - * @type string - */ - template: null, - /** - * The prefix used to generate an id automatically. Should be redefined in - * subclasses. If it is not defined, a default identifier will be used. - * - * @type string - */ - identifier_prefix: 'generic-identifier', - /** - * Base class for widgets. Handle rendering (based on a QWeb template), - * identifier generation, parenting and destruction of the widget. - * Also initialize the identifier. - * - * @constructs - * @params {openerp.base.search.BaseWidget} parent The parent widget. - */ - init: function (parent) { - this._super(parent); - this.make_id(this.identifier_prefix); - }, - /** - * Sets and returns a globally unique identifier for the widget. - * - * If a prefix is appended, the identifier will be appended to it. - * - * @params sections prefix sections, empty/falsy sections will be removed - */ - make_id: function () { - this.element_id = _.uniqueId(_.toArray(arguments).join('_')); - return this.element_id; - }, - /** - * "Starts" the widgets. Called at the end of the rendering, this allows - * to get a jQuery object referring to the DOM ($element attribute). - */ - start: function () { - this._super(); - var tmp = document.getElementById(this.element_id); - this.$element = tmp ? $(tmp) : null; - }, - /** - * "Stops" the widgets. Called when the view destroys itself, this - * lets the widgets clean up after themselves. - */ - stop: function () { - if(this.$element != null) { - this.$element.remove(); - } - this._super(); - }, - /** - * Render the widget. This.template must be defined. - * The content of the current object is passed as context to the template. - * - * @param {object} additional Additional context arguments to pass to the template. - */ - render: function (additional) { - return QWeb.render(this.template, _.extend({}, this, additional != null ? additional : {})); + warn: function(title, text) { + this.$element.notify('create', 'oe_notification_alert', { + title: title, + text: text + }); } }); @@ -877,7 +845,7 @@ openerp.base.WebClient = openerp.base.Controller.extend({ init: function(element_id) { this._super(null, element_id); - QWeb.add_template("xml/base.xml"); + QWeb.add_template("/base/static/src/xml/base.xml"); var params = {}; if(jQuery.param != undefined && jQuery.deparam(jQuery.param.querystring()).kitten != undefined) { this.$element.addClass("kitten-mode-activated"); @@ -889,7 +857,7 @@ openerp.base.WebClient = openerp.base.Controller.extend({ this.crashmanager = new openerp.base.CrashManager(this); this.crashmanager.start(false); - // Do you autorize this ? + // Do you autorize this ? will be replaced by notify() in controller openerp.base.Controller.prototype.notification = new openerp.base.Notification(this, "oe_notification"); this.header = new openerp.base.Header(this, "oe_header"); diff --git a/addons/base/static/src/js/controller.js b/addons/base/static/src/js/controller.js index 10355a267a3..54e876295f7 100644 --- a/addons/base/static/src/js/controller.js +++ b/addons/base/static/src/js/controller.js @@ -66,7 +66,7 @@ openerp.base.controller = function(instance) { return Class; }; -})(instance.base); +})(); // todo change john resig class to keep window clean instance.base.Class = window.Class @@ -121,14 +121,39 @@ instance.base.callback = function(obj, method) { }; /** - * OpenERP Controller + * Generates an inherited class that replaces all the methods by null methods (methods + * that does nothing and always return undefined). + * + * @param {Class} claz + * @param {dict} add Additional functions to override. + * @return {Class} */ -instance.base.BasicController = instance.base.Class.extend( /** @lends instance.base.BasicController# */{ +instance.base.generate_null_object_class = function(claz, add) { + var newer = {}; + var copy_proto = function(prototype) { + for (var name in prototype) { + if(typeof prototype[name] == "function") { + newer[name] = function() {}; + } + } + if (prototype.prototype) + copy_proto(prototype.prototype); + }; + copy_proto(claz.prototype); + newer.init = instance.base.Controller.prototype.init; + var tmpclass = claz.extend(newer); + return tmpclass.extend(add || {}); +}; + +/** + * OpenERP Controller + * TODO merge BaseWidget with Controller + */ +instance.base.Controller = instance.base.Class.extend( /** @lends instance.base.Controller# */{ /** + * @constructs * rpc operations, event binding and callback calling should be done in - * start() instead of init so that event can be hooked in between. - * - * @constructs + * start() instead of init so that events can be hooked in between. */ init: function(parent, element_id) { this.element_id = element_id; @@ -137,11 +162,14 @@ instance.base.BasicController = instance.base.Class.extend( /** @lends instance. instance.screen[element_id] = this; } // save the parent children relationship - this.children = []; - this.parent = parent; - if(this.parent && this.parent.children) { - this.parent.children.push(this); + this.controller_parent = parent; + this.controller_children = []; + if(parent && parent.controller_children) { + parent.controller_children.push(this); } + // backward compatibility + this.parent = this.controller_parent; + this.children = this.controller_children; // Transform on_* method into openerp.base.callbacks for (var name in this) { @@ -155,9 +183,8 @@ instance.base.BasicController = instance.base.Class.extend( /** @lends instance. } }, /** - * Controller start - * event binding, rpc and callback calling required to initialize the - * object can happen here + * Event binding, rpc and callback calling required to initialize the + * object should happen here * * Returns a promise object letting callers (subclasses and direct callers) * know when this component is done starting diff --git a/addons/base/static/src/js/list.js b/addons/base/static/src/js/list.js index 6bed26ce692..1c3b1cfdc4c 100644 --- a/addons/base/static/src/js/list.js +++ b/addons/base/static/src/js/list.js @@ -1086,6 +1086,45 @@ openerp.base.ListView.Groups = openerp.base.Class.extend( /** @lends openerp.bas }); return d.promise(); }, + setup_resequence_rows: function (list, dataset) { + // drag and drop enabled if list is not sorted and there is a + // "sequence" column in the view. + if ((dataset.sort && dataset.sort()) + || !_(this.columns).any(function (column) { + return column.name === 'sequence'; })) { + return; + } + // ondrop, move relevant record & fix sequences + list.$current.sortable({ + stop: function (event, ui) { + var from = ui.item.data('index'), + to = ui.item.prev().data('index') || 0; + if (from === to) { return; } + list.rows.splice(to, 0, list.rows.splice(from, 1)[0]); + + ui.item.parent().children().each(function (i, e) { + // reset record-index accelerators on rows and even/odd + var even = i%2 === 0; + $(e).data('index', i) + .toggleClass('even', even) + .toggleClass('odd', !even); + }); + + // resequencing time! + var data, index = to, + // if drag to 1st row (to = 0), start sequencing from 0 + // (exclusive lower bound) + seq = to ? list.rows[to - 1].data.sequence.value : 0; + while (++seq, data = list.rows[index++].data) { + data.sequence.value = seq; + // write are independent from one another, so we can just + // launch them all at the same time and we don't really + // give a fig about when they're done + dataset.write(data.id.value, {sequence: seq}); + } + } + }); + }, render: function (post_render) { var self = this; var $element = $(''); @@ -1104,6 +1143,7 @@ openerp.base.ListView.Groups = openerp.base.Class.extend( /** @lends openerp.bas self.children[null] = list; self.elements = [list.$current.replaceAll($element)[0]]; + self.setup_resequence_rows(list, dataset); if (post_render) { post_render(); } }); }); diff --git a/addons/base/static/src/js/views.js b/addons/base/static/src/js/views.js index 1ee17bdab4b..d01d847843e 100644 --- a/addons/base/static/src/js/views.js +++ b/addons/base/static/src/js/views.js @@ -18,6 +18,18 @@ openerp.base.ActionManager = openerp.base.Controller.extend({ * Process an action * Supported actions: act_window */ + action_window: function() { + }, + action_window_close: function() { + }, + action_server: function() { + }, + action_url: function() { + }, + action_report: function() { + }, + action_client: function() { + }, do_action: function(action, on_closed) { var self = this; action.flags = _.extend({ @@ -264,6 +276,7 @@ openerp.base.NullViewManager = openerp.base.generate_null_object_class(openerp.b } }); +// TODO Will move to action Manager openerp.base.ViewManagerAction = openerp.base.ViewManager.extend({ init: function(parent, element_id, action) { this.session = parent.session; diff --git a/openerpweb/openerpweb.py b/openerpweb/openerpweb.py index eee0a4eec20..1e5c3cf8b92 100644 --- a/openerpweb/openerpweb.py +++ b/openerpweb/openerpweb.py @@ -407,7 +407,7 @@ class Root(object): if path_addons not in sys.path: sys.path.insert(0, path_addons) for i in os.listdir(path_addons): - if i not in sys.modules: + if i not in addons_module: manifest_path = os.path.join(path_addons, i, '__openerp__.py') if os.path.isfile(manifest_path): manifest = eval(open(manifest_path).read()) @@ -445,7 +445,7 @@ class Root(object): #for the mobile web client we are supposed to use a different url to just add '/mobile' raise cherrypy.HTTPRedirect('/web_mobile/static/src/web_mobile.html', 301) else: - raise cherrypy.HTTPRedirect('/base/static/src/base.html', 301) + raise cherrypy.HTTPRedirect('/base/webclient/home', 301) default.exposed = True def main(argv):