diff --git a/addons/web/__openerp__.py b/addons/web/__openerp__.py index adf61f5facc..a66bd4687a7 100644 --- a/addons/web/__openerp__.py +++ b/addons/web/__openerp__.py @@ -53,4 +53,7 @@ "static/src/css/data_export.css", "static/src/css/data_import.css", ], + 'qweb' : [ + "static/src/xml/*.xml", + ], } diff --git a/addons/web/controllers/main.py b/addons/web/controllers/main.py index 6cc46b476a9..74285b1f444 100644 --- a/addons/web/controllers/main.py +++ b/addons/web/controllers/main.py @@ -24,19 +24,51 @@ openerpweb = web.common.http # OpenERP Web web Controllers #---------------------------------------------------------- -def concat_files(file_list): - """ Concatenate file content + +def concat_xml(file_list): + """Concatenate xml files return (concat,timestamp) concat: concatenation of file content timestamp: max(os.path.getmtime of file_list) """ + root = None + files_timestamp = 0 + for fname in file_list: + ftime = os.path.getmtime(fname) + if ftime > files_timestamp: + files_timestamp = ftime + + xml = ElementTree.parse(fname).getroot() + + if root is None: + root = ElementTree.Element(xml.tag) + #elif root.tag != xml.tag: + # raise ValueError("Root tags missmatch: %r != %r" % (root.tag, xml.tag)) + + for child in xml.getchildren(): + root.append(child) + return ElementTree.tostring(root, 'utf-8'), files_timestamp + + +def concat_files(file_list, reader=None): + """ Concatenate file content + return (concat,timestamp) + concat: concatenation of file content, read by `reader` + timestamp: max(os.path.getmtime of file_list) + """ + if reader is None: + def reader(f): + with open(f) as fp: + return fp.read() + files_content = [] files_timestamp = 0 for fname in file_list: ftime = os.path.getmtime(fname) if ftime > files_timestamp: files_timestamp = ftime - files_content.append(open(fname).read()) + + files_content.append(reader(fname)) files_concat = "".join(files_content) return files_concat,files_timestamp @@ -98,10 +130,42 @@ class WebClient(openerpweb.Controller): def jslist(self, req, mods=None): return self.manifest_list(req, mods, 'js') + @openerpweb.jsonrequest + def qweblist(self, req, mods=None): + return self.manifest_list(req, mods, 'qweb') + @openerpweb.httprequest def css(self, req, mods=None): - files = [f[0] for f in self.manifest_glob(req, mods, 'css')] - content,timestamp = concat_files(files) + + files = list(self.manifest_glob(req, mods, 'css')) + file_map = dict(files) + + rx_import = re.compile(r"""@import\s+('|")(?!'|"|/|https?://)""", re.U) + rx_url = re.compile(r"""url\s*\(\s*('|"|)(?!'|"|/|https?://)""", re.U) + + + def reader(f): + """read the a css file and absolutify all relative uris""" + with open(f) as fp: + data = fp.read() + + web_path = file_map[f] + web_dir = os.path.dirname(web_path) + + data = re.sub( + rx_import, + r"""@import \1%s/""" % (web_dir,), + data, + ) + + data = re.sub( + rx_url, + r"""url(\1%s/""" % (web_dir,), + data, + ) + return data + + content,timestamp = concat_files((f[0] for f in files), reader) # TODO use timestamp to set Last mofified date and E-tag return req.make_response(content, [('Content-Type', 'text/css')]) @@ -112,6 +176,14 @@ class WebClient(openerpweb.Controller): # TODO use timestamp to set Last mofified date and E-tag return req.make_response(content, [('Content-Type', 'application/javascript')]) + @openerpweb.httprequest + def qweb(self, req, mods=None): + files = [f[0] for f in self.manifest_glob(req, mods, 'qweb')] + content,timestamp = concat_xml(files) + # TODO use timestamp to set Last mofified date and E-tag + return req.make_response(content, [('Content-Type', 'text/xml')]) + + @openerpweb.httprequest def home(self, req, s_action=None, **kw): js = "\n ".join(''%i for i in self.manifest_list(req, None, 'js')) @@ -451,7 +523,7 @@ def clean_action(req, action, do_not_eval=False): # values come from the server, we can just eval them if isinstance(action.get('context'), basestring): action['context'] = eval( action['context'], eval_ctx ) or {} - + if isinstance(action.get('domain'), basestring): action['domain'] = eval( action['domain'], eval_ctx ) or [] else: diff --git a/addons/web/po/ar.po b/addons/web/po/ar.po index 0802e990ae5..0aac2c8aed2 100644 --- a/addons/web/po/ar.po +++ b/addons/web/po/ar.po @@ -8,13 +8,13 @@ msgstr "" "Project-Id-Version: openerp-web\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2011-10-07 10:38+0200\n" -"PO-Revision-Date: 2011-11-04 16:13+0000\n" -"Last-Translator: kifcaliph \n" +"PO-Revision-Date: 2011-11-08 05:44+0000\n" +"Last-Translator: Ahmad Khayyat \n" "Language-Team: Arabic \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2011-11-05 06:04+0000\n" +"X-Launchpad-Export-Date: 2011-11-08 06:12+0000\n" "X-Generator: Launchpad (build 14231)\n" #: addons/web/static/src/js/view_form.js:355 @@ -210,7 +210,8 @@ msgid "" "Today's enterprise challenges are multiple. We provide one module for each " "need." msgstr "" -"تتعدد تحديات المنشآت اليوم، و لذلك نوفر العديد من وحدات الأكواد لكل إحتياج." +"تتعدد تحديات المنشآت اليوم، و لذلك نوفر العديد من الوحدات البرمجية لكل " +"احتياج." #: addons/web/static/src/xml/base.xml:0 msgid "Open Source" @@ -265,7 +266,7 @@ msgstr "إخفاء هذه الملحوظة" #: addons/web/static/src/xml/base.xml:0 msgid "Disable all tips" -msgstr "تعطيل جميع النصائح" +msgstr "تعطيل جميع الإرشادات" #: addons/web/static/src/xml/base.xml:0 msgid "View#" diff --git a/addons/web/static/src/css/base.css b/addons/web/static/src/css/base.css index 3430dfe196a..94e36d60419 100644 --- a/addons/web/static/src/css/base.css +++ b/addons/web/static/src/css/base.css @@ -1088,7 +1088,7 @@ label.error { list-style: none; } .openerp .oe-view-manager-logs li:before { - content: '→ '; + content: '\2192 '; } .openerp .oe-view-manager-logs a { text-decoration: none; @@ -1448,3 +1448,4 @@ ul.oe-arrow-list li.oe-arrow-list-selected .oe-arrow-list-after { .openerp .oe_view_editor_tree_grid a { display: block; } + diff --git a/addons/web/static/src/js/chrome.js b/addons/web/static/src/js/chrome.js index 22c2b0ba2ea..d85942feead 100644 --- a/addons/web/static/src/js/chrome.js +++ b/addons/web/static/src/js/chrome.js @@ -136,10 +136,10 @@ openerp.web.Dialog = openerp.web.OldWidget.extend(/** @lends openerp.web.Dialog# } }); -openerp.web.CrashManager = openerp.web.SessionAware.extend({ - init: function(parent) { - this._super((parent || {}).session); - this.session.on_rpc_error.add(this.on_rpc_error); +openerp.web.CrashManager = openerp.web.CallbackEnabled.extend({ + init: function() { + this._super(); + openerp.connection.on_rpc_error.add(this.on_rpc_error); }, on_rpc_error: function(error) { this.error = error; @@ -959,17 +959,15 @@ openerp.web.WebClient = openerp.web.Widget.extend(/** @lends openerp.web.WebClie this._super(null, element_id); openerp.webclient = this; - QWeb.add_template("/web/static/src/xml/base.xml"); var params = {}; if(jQuery.param != undefined && jQuery.deparam(jQuery.param.querystring()).kitten != undefined) { this.$element.addClass("kitten-mode-activated"); } this.$element.html(QWeb.render("Interface", params)); - this.notification = new openerp.web.Notification(); - this.session = new openerp.web.Session(); + this.notification = new openerp.web.Notification(this); this.loading = new openerp.web.Loading(this,"oe_loading"); - this.crashmanager = new openerp.web.CrashManager(this); + this.crashmanager = new openerp.web.CrashManager(); this.header = new openerp.web.Header(this); this.login = new openerp.web.Login(this); diff --git a/addons/web/static/src/js/core.js b/addons/web/static/src/js/core.js index 9563abea49a..7b402cae7fd 100644 --- a/addons/web/static/src/js/core.js +++ b/addons/web/static/src/js/core.js @@ -237,8 +237,8 @@ openerp.web.Registry = openerp.web.Class.extend( /** @lends openerp.web.Registry * registry was created. * * An object path is simply a dotted name from the openerp root to the - * object pointed to (e.g. ``"openerp.web.Session"`` for an OpenERP - * session object). + * object pointed to (e.g. ``"openerp.web.Connection"`` for an OpenERP + * connection object). * * @constructs openerp.web.Registry * @param {Object} mapping a mapping of keys to object-paths @@ -338,9 +338,9 @@ openerp.web.CallbackEnabled = openerp.web.Class.extend(/** @lends openerp.web.Ca } }); -openerp.web.Session = openerp.web.CallbackEnabled.extend( /** @lends openerp.web.Session# */{ +openerp.web.Connection = openerp.web.CallbackEnabled.extend( /** @lends openerp.web.Connection# */{ /** - * @constructs openerp.web.Session + * @constructs openerp.web.Connection * @extends openerp.web.CallbackEnabled * * @param {String} [server] JSON-RPC endpoint hostname @@ -364,7 +364,9 @@ openerp.web.Session = openerp.web.CallbackEnabled.extend( /** @lends openerp.web // TODO: session should have an optional name indicating that they'll // be saved to (and revived from) cookies this.name = 'session'; + this.do_load_qweb(['/web/webclient/qweb']); }, + start: function() { this.session_restore(); }, @@ -487,9 +489,11 @@ openerp.web.Session = openerp.web.CallbackEnabled.extend( /** @lends openerp.web self.user_context = result.context; self.db = result.db; self.session_save(); + self.on_session_valid(); return true; }).then(success_callback); }, + login: function() { this.session_login.apply(this, arguments); }, /** * Reloads uid and session_id from local storage, if they exist */ @@ -577,6 +581,7 @@ openerp.web.Session = openerp.web.CallbackEnabled.extend( /** @lends openerp.web self.rpc('/web/webclient/jslist', {"mods": modules}, function(files) { self.do_load_js(file_list.concat(files)); }); + self.rpc('/web/webclient/qweblist', {"mods": modules}, self.do_load_qweb); openerp._modules_loaded = true; }); }); @@ -610,6 +615,12 @@ openerp.web.Session = openerp.web.CallbackEnabled.extend( /** @lends openerp.web this.on_modules_loaded(); } }, + do_load_qweb: function(files) { + var self = this; + _.each(files, function(file) { + openerp.web.qweb.add_template(file); + }); + }, on_modules_loaded: function() { for(var j=0; j'); + this.edit_widget = []; + _.each(properties, function(record) { + var id = record, + type_widget; + self.ready = $.when(self.on_groups(id)).then(function () { + if (_.include(widget,id)){ + type_widget = new (self.property.get_any(['undefined' , id, arch_val[0]['att_list'][0]])) (self.edit_node_dialog, arch_val, id); + } else { + type_widget = new openerp.web.ViewEditor.FieldChar (self.edit_node_dialog,arch_val, id); + } + self.edit_widget.push(type_widget); + self.edit_node_dialog.$element.find('table[id=rec_table]').append(''+id+':'+type_widget.render()+''); + var view_val = _.detect(arch_val[0]['att_list'],function(res) { + return _.include(res, id); + }); + var value = null; + id == 'groups'? type_widget.set_value(self.groups, view_val): type_widget.set_value(value, view_val); + type_widget.start(); + }); + }); + }, + //for getting groups + on_groups: function(id){ + var self = this, + def = $.Deferred(); + if (id != 'groups') { + self.groups = false; + return false; + } + var group_ids = [], + group_names = {}, + groups = []; + var res_groups = new openerp.web.DataSetSearch(this,'res.groups', null, null), + model_data = new openerp.web.DataSetSearch(self,'ir.model.data', null, null); + res_groups + .read_slice([], {}) + .done(function(res_grp) { + _.each(res_grp,function(res){ + var key = res.id; + group_names[key]=res.name; + group_ids.push(res.id); + }); + model_data + .read_slice([],{domain:[['res_id', 'in', group_ids],['model','=','res.groups']]}) + .done(function(model_grp) { + _.each(model_grp,function(res_group){ + groups.push([res_group.name,group_names[res_group.res_id]]); + }); + self.groups = groups; + def.resolve(); + }); + }) + return def.promise(); } }); +openerp.web.ViewEditor.Field = openerp.web.Class.extend({ + init: function(view, node, id) { + this.$element = view.$element; + this.node = node; + this.dirty = false; + this.name = id; + }, + on_ui_change: function() { + this.dirty = true; + }, + render: function() { + return QWeb.render(this.template, {widget: this}); + }, +}); +openerp.web.ViewEditor.FieldBoolean = openerp.web.ViewEditor.Field.extend({ + template : "vieweditor_boolean", + start: function() { + var self = this; + this.$element.find("input[id="+ self.name+"]").change(function() { + self.on_ui_change(); + }); + }, + set_value: function(value,view_val) { + if (view_val) { + this.$element.find("input[id=" + this.name+ "]").attr('checked', view_val[1]); + } + }, + get_value: function() { + if (!this.dirty) { + return false; + } + var value = this.$element.find("input[id=" + this.name + "]").is(':checked'); + return value ? [this.name, value] : [this.name, null]; + } +}); +openerp.web.ViewEditor.FieldChar = openerp.web.ViewEditor.Field.extend({ + template : "vieweditor_char", + start: function () { + var self = this; + this.$element.find("input[id="+ this.name+"]").css('width','100%').change(function() { + self.on_ui_change(); + }); + }, + set_value: function(value,view_val) { + view_val ? this.$element.find("input[id=" + this.name + "]").val(view_val[1]): this.$element.find("tr[id=" + this.name + "] input").val(); + }, + get_value: function() { + if (!this.dirty) { + return false; + } + var value= this.$element.find("input[id=" + this.name + "]").val(); + return value ? [this.name, value] : [this.name, ""]; + } +}); +openerp.web.ViewEditor.FieldSelect = openerp.web.ViewEditor.Field.extend({ + template : "vieweditor_selection", + start: function () { + var self = this; + this.$element.find("select[id=" + this.name + "]").css('width', '100%').change(function() { + self.on_ui_change(); + }); + }, + set_value: function(value, view_val) { + var self = this; + _.each(value, function(item) { + var select_val = view_val? (view_val[1] == ((typeof(item) == 'string')? item:item[0])?true: false): false; + self.$element.find("select[id=" + self.name + "]").append($("