backport stable branch
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Wed, 23 Dec 2009 12:20:10 +0100
changeset 4204 60256056bda6
parent 4192 8e2eaa6b3733 (current diff)
parent 4203 e972fc306719 (diff)
child 4211 eac4b23a42ff
backport stable branch
__pkginfo__.py
etwist/server.py
i18n/fr.po
server/migractions.py
server/serverctl.py
server/sources/__init__.py
server/sources/native.py
server/sqlutils.py
server/test/unittest_querier.py
--- a/.hgtags	Tue Dec 22 19:27:51 2009 +0100
+++ b/.hgtags	Wed Dec 23 12:20:10 2009 +0100
@@ -94,3 +94,5 @@
 d7f2d32340fb59753548ef29cbc1958ef3a55fc6 cubicweb-debian-version-3.5.9-1
 9b52725d8c534ba40877457b413077a10173bf88 cubicweb-version-3.5.10
 dfe2f245248c97bea3a29c8ecc6d293e25ff708e cubicweb-debian-version-3.5.10-1
+f48b2f193961803cf42147272671a335a2daeceb cubicweb-version-3.5.11
+4920121d41f28c8075a4f00461911677396fc566 cubicweb-debian-version-3.5.11-1
--- a/debian/changelog	Tue Dec 22 19:27:51 2009 +0100
+++ b/debian/changelog	Wed Dec 23 12:20:10 2009 +0100
@@ -1,3 +1,9 @@
+cubicweb (3.5.11-1) unstable; urgency=low
+
+  * new upstream release
+
+ -- Sylvain Thénault <sylvain.thenault@logilab.fr>  Wed, 23 Dec 2009 10:27:12 +0100
+
 cubicweb (3.5.10-1) unstable; urgency=low
 
   * new upstream release
--- a/debian/control	Tue Dec 22 19:27:51 2009 +0100
+++ b/debian/control	Wed Dec 23 12:20:10 2009 +0100
@@ -77,7 +77,7 @@
 Package: cubicweb-common
 Architecture: all
 XB-Python-Version: ${python:Versions}
-Depends: ${python:Depends}, graphviz, gettext, python-logilab-mtconverter (>= 0.6.0), python-logilab-common (>= 0.45.2), python-yams (>= 0.25.0), python-rql (>= 0.22.3), python-lxml
+Depends: ${python:Depends}, graphviz, gettext, python-logilab-mtconverter (>= 0.6.0), python-logilab-common (>= 0.46.0), python-yams (>= 0.25.0), python-rql (>= 0.22.3), python-lxml
 Recommends: python-simpletal (>= 4.0)
 Conflicts: cubicweb-core
 Replaces: cubicweb-core
--- a/etwist/server.py	Tue Dec 22 19:27:51 2009 +0100
+++ b/etwist/server.py	Wed Dec 23 12:20:10 2009 +0100
@@ -100,9 +100,11 @@
         self.debugmode = debug
         self.config = config
         self.base_url = config['base-url'] or config.default_base_url()
-        assert self.base_url[-1] == '/'
+        if self.base_url[-1] != '/':
+            self.base_url += '/'
         self.https_url = config['https-url']
-        assert not self.https_url or self.https_url[-1] == '/'
+        if self.https_url and self.https_url[-1] != '/':
+            self.https_url += '/'
         # instantiate publisher here and not in init_publisher to get some
         # checks done before daemonization (eg versions consistency)
         self.appli = CubicWebPublisher(config, debug=self.debugmode)
--- a/i18n/en.po	Tue Dec 22 19:27:51 2009 +0100
+++ b/i18n/en.po	Wed Dec 23 12:20:10 2009 +0100
@@ -3617,7 +3617,7 @@
 msgstr ""
 
 #, python-format
-msgid "transition %s isn't allowed from %s"
+msgid "transition %(tr)s isn't allowed from %(st)s"
 msgstr ""
 
 msgid "transition doesn't belong to entity's workflow"
--- a/i18n/es.po	Tue Dec 22 19:27:51 2009 +0100
+++ b/i18n/es.po	Wed Dec 23 12:20:10 2009 +0100
@@ -3716,7 +3716,7 @@
 msgstr "cambiar valor"
 
 #, python-format
-msgid "transition %s isn't allowed from %s"
+msgid "transition %(tr)s isn't allowed from %(st)s"
 msgstr ""
 
 msgid "transition doesn't belong to entity's workflow"
--- a/i18n/fr.po	Tue Dec 22 19:27:51 2009 +0100
+++ b/i18n/fr.po	Wed Dec 23 12:20:10 2009 +0100
@@ -3741,8 +3741,8 @@
 msgstr "inverser les cases à cocher"
 
 #, python-format
-msgid "transition %s isn't allowed from %s"
-msgstr "la transition %s n'est pas autorisée depuis l'état %s"
+msgid "transition %(tr)s isn't allowed from %(st)s"
+msgstr "la transition %(tr)s n'est pas autorisée depuis l'état %(st)s"
 
 msgid "transition doesn't belong to entity's workflow"
 msgstr "la transition n'appartient pas au workflow de l'entité"
--- a/server/migractions.py	Tue Dec 22 19:27:51 2009 +0100
+++ b/server/migractions.py	Wed Dec 23 12:20:10 2009 +0100
@@ -196,7 +196,7 @@
             if systemonly and source.uri != 'system':
                 continue
             try:
-                source.restore(osp.join(tmpdir, source.uri), drop=drop)
+                source.restore(osp.join(tmpdir, source.uri), self.confirm, drop)
             except Exception, exc:
                 print '-> error trying to restore [%s]' % exc
                 if not self.confirm('Continue anyway?', default='n'):
--- a/server/serverctl.py	Tue Dec 22 19:27:51 2009 +0100
+++ b/server/serverctl.py	Wed Dec 23 12:20:10 2009 +0100
@@ -55,8 +55,11 @@
             password = source['db-password']
         else:
             password = getpass('password: ')
+    extra_args = source.get('db-extra-arguments')
+    extra = extra_args and {'extra_args': extra_args} or {}
     return get_connection(driver, dbhost, dbname, user, password=password,
-                          port=source.get('db-port'))
+                          port=source.get('db-port'),
+                          **extra)
 
 def system_source_cnx(source, dbms_system_base=False,
                       special_privs='CREATE/DROP DATABASE', verbose=True):
@@ -281,11 +284,16 @@
         automatic = self.get('automatic')
         appid = pop_arg(args, msg='No instance specified !')
         config = ServerConfiguration.config_for(appid)
-        create_db = self.config.create_db
         source = config.sources()['system']
+        dbname = source['db-name']
         driver = source['db-driver']
+        create_db = self.config.create_db
         helper = get_adv_func_helper(driver)
-        if create_db:
+        if driver == 'sqlite':
+            if os.path.exists(dbname) and automatic or \
+                   ASK.confirm('Database %s already exists -- do you want to drop it ?' % dbname):
+                os.unlink(dbname)
+        elif create_db:
             print '\n'+underline_title('Creating the system database')
             # connect on the dbms system base to create our base
             dbcnx = _db_sys_cnx(source, 'CREATE DATABASE and / or USER', verbose=verbose)
@@ -297,7 +305,6 @@
                            ASK.confirm('Create db user %s ?' % user, default_is_yes=False)):
                         helper.create_user(source['db-user'], source['db-password'])
                         print '-> user %s created.' % user
-                dbname = source['db-name']
                 if dbname in helper.list_databases(cursor):
                     if automatic or ASK.confirm('Database %s already exists -- do you want to drop it ?' % dbname):
                         cursor.execute('DROP DATABASE %s' % dbname)
@@ -310,7 +317,7 @@
                     helper.create_database(cursor, dbname,
                                            encoding=source['db-encoding'])
                 dbcnx.commit()
-                print '-> database %s created.' % source['db-name']
+                print '-> database %s created.' % dbname
             except:
                 dbcnx.rollback()
                 raise
@@ -364,10 +371,13 @@
         config = ServerConfiguration.config_for(appid)
         try:
             system = config.sources()['system']
+            extra_args=system.get('db-extra-arguments')
+            extra = extra_args and {'extra_args': extra_args} or {}
             get_connection(
                 system['db-driver'], database=system['db-name'],
                 host=system.get('db-host'), port=system.get('db-port'),
-                user=system.get('db-user'), password=system.get('db-password'))
+                user=system.get('db-user'), password=system.get('db-password'), 
+                **extra)
         except Exception, ex:
             raise ConfigurationError(
                 'You seem to have provided wrong connection information in '\
--- a/server/sources/__init__.py	Tue Dec 22 19:27:51 2009 +0100
+++ b/server/sources/__init__.py	Wed Dec 23 12:20:10 2009 +0100
@@ -104,7 +104,7 @@
         """method called to create a backup of source's data"""
         pass
 
-    def restore(self, backupfile):
+    def restore(self, backupfile, confirm, drop):
         """method called to restore a backup of source's data"""
         pass
 
--- a/server/sources/extlite.py	Tue Dec 22 19:27:51 2009 +0100
+++ b/server/sources/extlite.py	Wed Dec 23 12:20:10 2009 +0100
@@ -101,11 +101,11 @@
         finally:
             self.open_pool_connections()
 
-    def restore(self, backupfile, drop):
+    def restore(self, backupfile, confirm, drop):
         """method called to restore a backup of source's data"""
         self.close_pool_connections()
         try:
-            self.sqladapter.restore_from_file(backupfile, drop)
+            self.sqladapter.restore_from_file(backupfile, confirm, drop)
         finally:
             self.open_pool_connections()
 
--- a/server/sources/native.py	Tue Dec 22 19:27:51 2009 +0100
+++ b/server/sources/native.py	Wed Dec 23 12:20:10 2009 +0100
@@ -215,12 +215,12 @@
         finally:
             self.open_pool_connections()
 
-    def restore(self, backupfile, drop):
+    def restore(self, backupfile, confirm, drop):
         """method called to restore a backup of source's data"""
         if self.repo.config.open_connections_pools:
             self.close_pool_connections()
         try:
-            self.restore_from_file(backupfile, drop)
+            self.restore_from_file(backupfile, confirm, drop=drop)
         finally:
             if self.repo.config.open_connections_pools:
                 self.open_pool_connections()
@@ -579,7 +579,7 @@
 
 def sql_schema(driver):
     helper = get_adv_func_helper(driver)
-    tstamp_col_type = helper.TYPE_MAPPING.get('TIMESTAMP', 'TIMESTAMP')
+    tstamp_col_type = helper.TYPE_MAPPING['Datetime']
     schema = """
 /* Create the repository's system database */
 
@@ -668,7 +668,7 @@
                 raise AuthenticationError('bad login')
             # passwords are stored using the Bytes type, so we get a StringIO
             if pwd is not None:
-                args['pwd'] = crypt_password(password, pwd.getvalue()[:2])
+                args['pwd'] = Binary(crypt_password(password, pwd.getvalue()[:2]))
         # get eid from login and (crypted) password
         rset = self.source.syntax_tree_search(session, self._auth_rqlst, args)
         try:
--- a/server/sqlutils.py	Tue Dec 22 19:27:51 2009 +0100
+++ b/server/sqlutils.py	Wed Dec 23 12:20:10 2009 +0100
@@ -144,6 +144,7 @@
         self.dbpasswd = source_config.get('db-password')
         self.encoding = source_config.get('db-encoding', 'UTF-8')
         self.dbapi_module = db.get_dbapi_compliant_module(self.dbdriver)
+        self.dbdriver_extra_args = source_config.get('db-extra-arguments')
         self.binary = self.dbapi_module.Binary
         self.dbhelper = self.dbapi_module.adv_func_helper
         self.sqlgen = SQLGenerator()
@@ -156,10 +157,14 @@
         else:
             self.info('connecting to %s@%s', self.dbname,
                       self.dbhost or 'localhost')
+        extra = {}
+        if self.dbdriver_extra_args:
+            extra = {'extra_args': self.dbdriver_extra_args}
         cnx = self.dbapi_module.connect(self.dbhost, self.dbname,
                                         user or self.dbuser,
                                         password or self.dbpasswd,
-                                        port=self.dbport)
+                                        port=self.dbport,
+                                        **extra)
         init_cnx(self.dbdriver, cnx)
         #self.dbapi_module.type_code_test(cnx.cursor())
         return cnx
@@ -178,7 +183,9 @@
                                                   keepownership=False,
                                                   drop=drop):
             if os.system(cmd):
-                raise Exception('Failed command: %s' % cmd)
+                print '-> Failed command: %s' % cmd
+                if not confirm('Continue anyway?', default='n'):
+                    raise Exception('Failed command: %s' % cmd)
 
     def merge_args(self, args, query_args):
         if args is not None:
@@ -241,6 +248,7 @@
                         value = value.getvalue()
                     else:
                         value = crypt_password(value)
+                    value = self.binary(value)
                 # XXX needed for sqlite but I don't think it is for other backends
                 elif atype == 'Datetime' and isinstance(value, date):
                     value = todatetime(value)
--- a/server/test/unittest_querier.py	Tue Dec 22 19:27:51 2009 +0100
+++ b/server/test/unittest_querier.py	Wed Dec 23 12:20:10 2009 +0100
@@ -1080,7 +1080,8 @@
                        % (SQL_PREFIX, SQL_PREFIX, SQL_PREFIX))
         passwd = str(cursor.fetchone()[0])
         self.assertEquals(passwd, crypt_password('toto', passwd[:2]))
-        rset = self.execute("Any X WHERE X is CWUser, X login 'bob', X upassword '%s'" % passwd)
+        rset = self.execute("Any X WHERE X is CWUser, X login 'bob', X upassword %(pwd)s",
+                            {'pwd': Binary(passwd)})
         self.assertEquals(len(rset.rows), 1)
         self.assertEquals(rset.description, [('CWUser',)])
 
@@ -1094,7 +1095,8 @@
                        % (SQL_PREFIX, SQL_PREFIX, SQL_PREFIX))
         passwd = str(cursor.fetchone()[0])
         self.assertEquals(passwd, crypt_password('tutu', passwd[:2]))
-        rset = self.execute("Any X WHERE X is CWUser, X login 'bob', X upassword '%s'" % passwd)
+        rset = self.execute("Any X WHERE X is CWUser, X login 'bob', X upassword %(pwd)s",
+                            {'pwd': Binary(passwd)})
         self.assertEquals(len(rset.rows), 1)
         self.assertEquals(rset.description, [('CWUser',)])
 
--- a/web/data/cubicweb.ajax.js	Tue Dec 22 19:27:51 2009 +0100
+++ b/web/data/cubicweb.ajax.js	Wed Dec 23 12:20:10 2009 +0100
@@ -13,54 +13,80 @@
     var loaded = [];
     var jqtagfilter = tag + '[' + srcattr + ']';
     jQuery('head ' + jqtagfilter).each(function(i) {
-	loaded.push(this.getAttribute(srcattr));
+        loaded.push(this.getAttribute(srcattr));
     });
     node.find(tag).each(function(i) {
-	if (this.getAttribute(srcattr)) {
-	    if (!loaded.contains(this.getAttribute(srcattr))) {
-		jQuery(this).appendTo(head);
-	    }
-	} else {
-	    jQuery(this).appendTo(head);
-	}
+        if (this.getAttribute(srcattr)) {
+            if (!loaded.contains(this.getAttribute(srcattr))) {
+                jQuery(this).appendTo(head);
+            }
+        } else {
+            jQuery(this).appendTo(head);
+        }
     });
     node.find(jqtagfilter).remove();
 }
 
 /*
- * inspect dom response, search for a <div class="ajaxHtmlHead"> node and
- * put its content into the real document's head.
+ * inspect dom response (as returned by getDomFromResponse), search for
+ * a <div class="ajaxHtmlHead"> node and put its content into the real
+ * document's head.
  * This enables dynamic css and js loading and is used by replacePageChunk
  */
-function loadAjaxHtmlHead(node) {
-    var head = jQuery('head');
-    node = jQuery(node).find('div.ajaxHtmlHead');
-    _loadAjaxHtmlHead(node, head, 'script', 'src');
-    _loadAjaxHtmlHead(node, head, 'link', 'href');
-    node.find('*').appendTo(head);
+function loadAjaxHtmlHead(response) {
+    var $head = jQuery('head');
+    var $responseHead = jQuery(response).find('div.ajaxHtmlHead');
+    // no ajaxHtmlHead found, no processing required
+    if (!$responseHead.length) {
+        return response;
+    }
+    _loadAjaxHtmlHead($responseHead, $head, 'script', 'src');
+    _loadAjaxHtmlHead($responseHead, $head, 'link', 'href');
+    // add any remaining children (e.g. meta)
+    $responseHead.children().appendTo($head);
+    // remove original container, which is now empty
+    $responseHead.remove();
+    // if there was only one actual node in the reponse besides
+    // the ajaxHtmlHead, then remove the wrapper created by
+    // getDomFromResponse() and return this single element
+    // For instance :
+    // 1/ CW returned the following content :
+    //    <div>the-actual-content</div><div class="ajaxHtmlHead">...</div>
+    // 2/ getDomFromReponse() wrapped this into a single DIV to hold everything
+    //    in one, unique, dom element
+    // 3/ now that we've removed the ajaxHtmlHead div, the only
+    //    node left in the wrapper if the 'real' node built by the view,
+    //    we can safely return this node. Otherwise, the view itself
+    //    returned several 'root' nodes and we need to keep the wrapper
+    //    created by getDomFromResponse()
+    if (response.childNodes.length == 1 &&
+	response.getAttribute('cubicweb:type') == 'cwResponseWrapper') {
+        return response.firstChild;
+    }
+    return response;
 }
 
 function preprocessAjaxLoad(node, newdomnode) {
-    loadAjaxHtmlHead(newdomnode);
+    return loadAjaxHtmlHead(newdomnode);
 }
 
 function postAjaxLoad(node) {
     // find sortable tables if there are some
     if (typeof(Sortable) != 'undefined') {
-	Sortable.sortTables(node);
+        Sortable.sortTables(node);
     }
     // find textareas and wrap them if there are some
     if (typeof(FCKeditor) != 'undefined') {
-	buildWysiwygEditors();
+        buildWysiwygEditors();
     }
     if (typeof initFacetBoxEvents != 'undefined') {
-	initFacetBoxEvents(node);
+        initFacetBoxEvents(node);
     }
     if (typeof buildWidgets != 'undefined') {
-	buildWidgets(node);
+        buildWidgets(node);
     }
     if (typeof roundedCorners != 'undefined') {
-	roundedCorners(node);
+        roundedCorners(node);
     }
     if (typeof setFormsTarget != 'undefined') {
        setFormsTarget(node);
@@ -71,7 +97,7 @@
     // we probably need to unbind the fired events
     // When this is done, jquery.treeview.js (for instance)
     // can be unpatched.
-  jQuery(CubicWeb).trigger('ajax-loaded');
+    jQuery(CubicWeb).trigger('ajax-loaded');
 }
 
 /* cubicweb loadxhtml plugin to make jquery handle xhtml response
@@ -87,38 +113,38 @@
 jQuery.fn.loadxhtml = function(url, data, reqtype, mode) {
     var ajax = null;
     if (reqtype == 'post') {
-	ajax = jQuery.post;
+        ajax = jQuery.post;
     } else {
-	ajax = jQuery.get;
+        ajax = jQuery.get;
     }
     if (this.size() > 1) {
-	log('loadxhtml was called with more than one element');
+        log('loadxhtml was called with more than one element');
     }
     var node = this.get(0); // only consider the first element
     mode = mode || 'replace';
     var callback = null;
     if (data && data.callback) {
-	callback = data.callback;
-	delete data.callback;
+        callback = data.callback;
+        delete data.callback;
     }
     ajax(url, data, function(response) {
-	var domnode = getDomFromResponse(response);
-	preprocessAjaxLoad(node, domnode);
-	if (mode == 'swap') {
-	    var origId = node.id;
-	    node = swapDOM(node, domnode);
-	    if (!node.id) {
-		node.id = origId;
-	    }
-	} else if (mode == 'replace') {
-	    jQuery(node).empty().append(domnode);
-	} else if (mode == 'append') {
-	    jQuery(node).append(domnode);
-	}
-	postAjaxLoad(node);
-	while (jQuery.isFunction(callback)) {
-	    callback = callback.apply(this, [domnode]);
-	}
+        var domnode = getDomFromResponse(response);
+        domnode = preprocessAjaxLoad(node, domnode);
+        if (mode == 'swap') {
+            var origId = node.id;
+            node = swapDOM(node, domnode);
+            if (!node.id) {
+                node.id = origId;
+            }
+        } else if (mode == 'replace') {
+            jQuery(node).empty().append(domnode);
+        } else if (mode == 'append') {
+            jQuery(node).append(domnode);
+        }
+        postAjaxLoad(node);
+        while (jQuery.isFunction(callback)) {
+            callback = callback.apply(this, [domnode]);
+        }
     });
 };
 
@@ -129,43 +155,43 @@
  */
 function loadDynamicFragments(node) {
     if (node) {
-	var fragments = jQuery(node).find('div.dynamicFragment');
+        var fragments = jQuery(node).find('div.dynamicFragment');
     } else {
-	var fragments = jQuery('div.dynamicFragment');
+        var fragments = jQuery('div.dynamicFragment');
     }
     if (fragments.length == 0) {
-	return;
+        return;
     }
     if (typeof LOADING_MSG == 'undefined') {
-	LOADING_MSG = 'loading'; // this is only a safety belt, it should not happen
+        LOADING_MSG = 'loading'; // this is only a safety belt, it should not happen
     }
     for(var i=0; i<fragments.length; i++) {
-	var fragment = fragments[i];
-	fragment.innerHTML = '<h3>' + LOADING_MSG + ' ... <img src="data/loading.gif" /></h3>';
-	// if cubicweb:loadurl is set, just pick the url et send it to loadxhtml
-	var url = getNodeAttribute(fragment, 'cubicweb:loadurl');
-	if (url) {
-	    jQuery(fragment).loadxhtml(url);
-	    continue;
-	}
-	// else: rebuild full url by fetching cubicweb:rql, cubicweb:vid, etc.
-	var rql = getNodeAttribute(fragment, 'cubicweb:rql');
-	var items = getNodeAttribute(fragment, 'cubicweb:vid').split('&');
-	var vid = items[0];
+        var fragment = fragments[i];
+        fragment.innerHTML = '<h3>' + LOADING_MSG + ' ... <img src="data/loading.gif" /></h3>';
+        // if cubicweb:loadurl is set, just pick the url et send it to loadxhtml
+        var url = getNodeAttribute(fragment, 'cubicweb:loadurl');
+        if (url) {
+            jQuery(fragment).loadxhtml(url);
+            continue;
+        }
+        // else: rebuild full url by fetching cubicweb:rql, cubicweb:vid, etc.
+        var rql = getNodeAttribute(fragment, 'cubicweb:rql');
+        var items = getNodeAttribute(fragment, 'cubicweb:vid').split('&');
+        var vid = items[0];
         var extraparams = {};
-	// case where vid='myvid&param1=val1&param2=val2': this is a deprecated abuse-case
-	if (items.length > 1) {
-	    console.log("[3.5] you're using extraargs in cubicweb:vid attribute, this is deprecated, consider using loadurl instead");
-	    for (var j=1; j<items.length; j++) {
-		var keyvalue = items[j].split('=');
-		extraparams[keyvalue[0]] = keyvalue[1];
-	    }
-	}
-	var actrql = getNodeAttribute(fragment, 'cubicweb:actualrql');
-	if (actrql) { extraparams['actualrql'] = actrql; }
-	var fbvid = getNodeAttribute(fragment, 'cubicweb:fallbackvid');
-	if (fbvid) { extraparams['fallbackvid'] = fbvid; }
-	replacePageChunk(fragment.id, rql, vid, extraparams);
+        // case where vid='myvid&param1=val1&param2=val2': this is a deprecated abuse-case
+        if (items.length > 1) {
+            console.log("[3.5] you're using extraargs in cubicweb:vid attribute, this is deprecated, consider using loadurl instead");
+            for (var j=1; j<items.length; j++) {
+                var keyvalue = items[j].split('=');
+                extraparams[keyvalue[0]] = keyvalue[1];
+            }
+        }
+        var actrql = getNodeAttribute(fragment, 'cubicweb:actualrql');
+        if (actrql) { extraparams['actualrql'] = actrql; }
+        var fbvid = getNodeAttribute(fragment, 'cubicweb:fallbackvid');
+        if (fbvid) { extraparams['fallbackvid'] = fbvid; }
+        replacePageChunk(fragment.id, rql, vid, extraparams);
     }
 }
 
@@ -175,9 +201,9 @@
 
 function remoteCallFailed(err, req) {
     if (req.status == 500) {
-	updateMessage(err);
+        updateMessage(err);
     } else {
-	updateMessage(_("an error occured while processing your request"));
+        updateMessage(_("an error occured while processing your request"));
     }
 }
 
@@ -193,10 +219,10 @@
 function remoteExec(fname /* ... */) {
     setProgressCursor();
     var props = {'fname' : fname, 'pageid' : pageid,
-     		 'arg': map(jQuery.toJSON, sliceList(arguments, 1))};
+                      'arg': map(jQuery.toJSON, sliceList(arguments, 1))};
     var result  = jQuery.ajax({url: JSON_BASE_URL, data: props, async: false}).responseText;
     if (result) {
-	result = evalJSON(result);
+        result = evalJSON(result);
     }
     resetCursor();
     return result;
@@ -216,7 +242,7 @@
 function asyncRemoteExec(fname /* ... */) {
     setProgressCursor();
     var props = {'fname' : fname, 'pageid' : pageid,
-     		 'arg': map(jQuery.toJSON, sliceList(arguments, 1))};
+                      'arg': map(jQuery.toJSON, sliceList(arguments, 1))};
     var deferred = loadRemote(JSON_BASE_URL, props, 'POST');
     deferred = deferred.addErrback(remoteCallFailed);
     deferred = deferred.addErrback(resetCursor);
@@ -243,9 +269,9 @@
 
 function openHash() {
     if (document.location.hash) {
-	var nid = document.location.hash.replace('#', '');
-	var node = jQuery('#' + nid);
-	if (node) { removeElementClass(node, "hidden"); }
+        var nid = document.location.hash.replace('#', '');
+        var node = jQuery('#' + nid);
+        if (node) { removeElementClass(node, "hidden"); }
     };
 }
 jQuery(document).ready(openHash);
@@ -258,18 +284,18 @@
     var node = getNode(nodeid);
     var d = asyncRemoteExec('component', compid, rql, registry, extraargs);
     d.addCallback(function(result, req) {
-	var domnode = getDomFromResponse(result);
-	if (node) {
-	    // make sure the component is visible
-	    removeElementClass(node, "hidden");
-	    swapDOM(node, domnode);
-	    postAjaxLoad(domnode);
-	}
+        var domnode = getDomFromResponse(result);
+        if (node) {
+            // make sure the component is visible
+            removeElementClass(node, "hidden");
+            swapDOM(node, domnode);
+            postAjaxLoad(domnode);
+        }
     });
     d.addCallback(resetCursor);
     d.addErrback(function(xxx) {
-	updateMessage(_("an error occured"));
-	log(xxx);
+        updateMessage(_("an error occured"));
+        log(xxx);
     });
   return d;
 }
@@ -282,28 +308,28 @@
 function userCallbackThenUpdateUI(cbname, compid, rql, msg, registry, nodeid) {
     var d = asyncRemoteExec('user_callback', cbname);
     d.addCallback(function() {
-	reloadComponent(compid, rql, registry, nodeid);
-	if (msg) { updateMessage(msg); }
+        reloadComponent(compid, rql, registry, nodeid);
+        if (msg) { updateMessage(msg); }
     });
     d.addCallback(resetCursor);
     d.addErrback(function(xxx) {
-	updateMessage(_("an error occured"));
-	log(xxx);
-	return resetCursor();
+        updateMessage(_("an error occured"));
+        log(xxx);
+        return resetCursor();
     });
 }
 
 function userCallbackThenReloadPage(cbname, msg) {
     var d = asyncRemoteExec('user_callback', cbname);
     d.addCallback(function() {
-	window.location.reload();
-	if (msg) { updateMessage(msg); }
+        window.location.reload();
+        if (msg) { updateMessage(msg); }
     });
     d.addCallback(resetCursor);
     d.addErrback(function(xxx) {
-	updateMessage(_("an error occured"));
-	log(xxx);
-	return resetCursor();
+        updateMessage(_("an error occured"));
+        log(xxx);
+        return resetCursor();
     });
 }
 
@@ -315,9 +341,9 @@
     var d = asyncRemoteExec('unregister_user_callback', cbname);
     d.addCallback(function() {resetCursor();});
     d.addErrback(function(xxx) {
-	updateMessage(_("an error occured"));
-	log(xxx);
-	return resetCursor();
+        updateMessage(_("an error occured"));
+        log(xxx);
+        return resetCursor();
     });
 }
 
@@ -333,25 +359,25 @@
 function replacePageChunk(nodeId, rql, vid, extraparams, /* ... */ swap, callback) {
     var params = null;
     if (callback) {
-	params = {callback: callback};
+        params = {callback: callback};
     }
 
     var node = jQuery('#' + nodeId)[0];
     var props = {};
     if (node) {
-	props['rql'] = rql;
-	props['fname'] = 'view';
-	props['pageid'] = pageid;
-	if (vid) { props['vid'] = vid; }
-	if (extraparams) { jQuery.extend(props, extraparams); }
-	// FIXME we need to do asURL(props) manually instead of
-	// passing `props` directly to loadxml because replacePageChunk
-	// is sometimes called (abusively) with some extra parameters in `vid`
-	var mode = swap?'swap':'replace';
-	var url = JSON_BASE_URL + asURL(props);
-	jQuery(node).loadxhtml(url, params, 'get', mode);
+        props['rql'] = rql;
+        props['fname'] = 'view';
+        props['pageid'] = pageid;
+        if (vid) { props['vid'] = vid; }
+        if (extraparams) { jQuery.extend(props, extraparams); }
+        // FIXME we need to do asURL(props) manually instead of
+        // passing `props` directly to loadxml because replacePageChunk
+        // is sometimes called (abusively) with some extra parameters in `vid`
+        var mode = swap?'swap':'replace';
+        var url = JSON_BASE_URL + asURL(props);
+        jQuery(node).loadxhtml(url, params, 'get', mode);
     } else {
-	log('Node', nodeId, 'not found');
+        log('Node', nodeId, 'not found');
     }
 }
 
@@ -374,20 +400,20 @@
  */
 function buildWysiwygEditors(parent) {
     jQuery('textarea').each(function () {
-	if (this.getAttribute('cubicweb:type') == 'wysiwyg') {
+        if (this.getAttribute('cubicweb:type') == 'wysiwyg') {
             // mark editor as instanciated, we may be called a number of times
             // (see postAjaxLoad)
             this.setAttribute('cubicweb:type', 'fckeditor');
-	    if (typeof FCKeditor != "undefined") {
-		var fck = new FCKeditor(this.id);
-		fck.Config['CustomConfigurationsPath'] = fckconfigpath;
-		fck.Config['DefaultLanguage'] = fcklang;
-		fck.BasePath = "fckeditor/";
-		fck.ReplaceTextarea();
-	    } else {
-		log('fckeditor could not be found.');
-	    }
-	}
+            if (typeof FCKeditor != "undefined") {
+                var fck = new FCKeditor(this.id);
+                fck.Config['CustomConfigurationsPath'] = fckconfigpath;
+                fck.Config['DefaultLanguage'] = fcklang;
+                fck.BasePath = "fckeditor/";
+                fck.ReplaceTextarea();
+            } else {
+                log('fckeditor could not be found.');
+            }
+        }
     });
 }
 
@@ -400,12 +426,12 @@
 function stripEmptyTextNodes(nodelist) {
     var stripped = [];
     for (var i=0; i < nodelist.length; i++) {
-	var node = nodelist[i];
-	if (isTextNode(node) && !node.textContent.strip()) {
-	    continue;
-	} else {
-	    stripped.push(node);
-	}
+        var node = nodelist[i];
+        if (isTextNode(node) && !node.textContent.strip()) {
+            continue;
+        } else {
+            stripped.push(node);
+        }
     }
     return stripped;
 }
@@ -415,25 +441,25 @@
  * */
 function getDomFromResponse(response) {
     if (typeof(response) == 'string') {
-	var doc = html2dom(response);
+        var doc = html2dom(response);
     } else {
         var doc = response.documentElement;
     }
     var children = doc.childNodes;
     if (!children.length) {
-	// no child (error cases) => return the whole document
-	return jQuery(doc).clone().context;
+        // no child (error cases) => return the whole document
+        return jQuery(doc).clone().context;
     }
     children = stripEmptyTextNodes(children);
     if (children.length == 1) {
-	// only one child => return it
-	return jQuery(children[0]).clone().context;
+        // only one child => return it
+        return jQuery(children[0]).clone().context;
     }
     // several children => wrap them in a single node and return the wrap
-    return DIV(null, map(function(node) {
-                           return jQuery(node).clone().context;
-                         },
-                         children));
+    return DIV({'cubicweb:type': "cwResponseWrapper"},
+               map(function(node) {
+                    return jQuery(node).clone().context;
+            }, children));
 }
 
 function postJSON(url, data, callback) {