merge
authorAurelien Campeas <aurelien.campeas@logilab.fr>
Thu, 08 Jul 2010 16:30:19 +0200
changeset 5946 9cbde75fefe8
parent 5944 b962dff47c36 (diff)
parent 5945 846d1fb32aa8 (current diff)
child 5947 e13e7833a57a
merge
web/views/reledit.py
--- a/.hgtags	Thu Jul 08 16:29:51 2010 +0200
+++ b/.hgtags	Thu Jul 08 16:30:19 2010 +0200
@@ -135,3 +135,5 @@
 5d05b08adeab1ea301e49ed8537e35ede6db92f6 cubicweb-debian-version-3.8.5-1
 1a24c62aefc5e57f61be3d04affd415288e81904 cubicweb-version-3.8.6
 607a90073911b6bb941a49b5ec0b0d2a9cd479af cubicweb-debian-version-3.8.6-1
+d9936c39d478b6701a4adef17bc28888ffa011c6 cubicweb-version-3.9.0
+eda4940ffef8b7d36127e68de63a52388374a489 cubicweb-debian-version-3.9.0-1
--- a/cwconfig.py	Thu Jul 08 16:29:51 2010 +0200
+++ b/cwconfig.py	Thu Jul 08 16:30:19 2010 +0200
@@ -991,6 +991,29 @@
         """write down current configuration"""
         self.generate_config(open(self.main_config_file(), 'w'))
 
+    def check_writeable_uid_directory(self, path):
+        """check given directory path exists, belongs to the user running the
+        server process and is writeable.
+
+        If not, try to fix this, leting exception propagate when not possible.
+        """
+        if not exists(path):
+            os.makedirs(path)
+        if self['uid']:
+            try:
+                uid = int(self['uid'])
+            except ValueError:
+                from pwd import getpwnam
+                uid = getpwnam(self['uid']).pw_uid
+        else:
+            uid = os.getuid()
+        fstat = os.stat(path)
+        if fstat.st_uid != uid:
+            os.chown(path, uid, os.getgid())
+        import stat
+        if not (fstat.st_mode & stat.S_IWUSR):
+            os.chmod(path, fstat.st_mode | stat.S_IWUSR)
+
     @cached
     def instance_md5_version(self):
         import hashlib
--- a/etwist/server.py	Thu Jul 08 16:29:51 2010 +0200
+++ b/etwist/server.py	Thu Jul 08 16:30:19 2010 +0200
@@ -38,11 +38,11 @@
 from twisted.web import static, resource
 from twisted.web.server import NOT_DONE_YET
 
-from cubicweb.web import dumps
 
 from logilab.common.decorators import monkeypatch
 
 from cubicweb import AuthenticationError, ConfigurationError, CW_EVENT_MANAGER
+from cubicweb.utils import json_dumps
 from cubicweb.web import Redirect, DirectResponse, StatusResponse, LogOut
 from cubicweb.web.application import CubicWebPublisher
 from cubicweb.web.http_headers import generateDateTime
@@ -317,12 +317,12 @@
         self.setResponseCode(http.BAD_REQUEST)
         if path in JSON_PATHS: # XXX better json path detection
             self.setHeader('content-type',"application/json")
-            body = dumps({'reason': 'request max size exceeded'})
+            body = json_dumps({'reason': 'request max size exceeded'})
         elif path in FRAME_POST_PATHS: # XXX better frame post path detection
             self.setHeader('content-type',"text/html")
             body = ('<script type="text/javascript">'
                     'window.parent.handleFormValidationResponse(null, null, null, %s, null);'
-                    '</script>' % dumps( (False, 'request max size exceeded', None) ))
+                    '</script>' % json_dumps( (False, 'request max size exceeded', None) ))
         else:
             self.setHeader('content-type',"text/html")
             body = ("<html><head><title>Processing Failed</title></head><body>"
@@ -402,6 +402,7 @@
 def run(config, vreg=None, debug=None):
     if debug is not None:
         config.debugmode = debug
+    config.check_writeable_uid_directory(config.appdatahome)
     # create the site
     root_resource = CubicWebRootResource(config, vreg=vreg)
     website = server.Site(root_resource)
--- a/selectors.py	Thu Jul 08 16:29:51 2010 +0200
+++ b/selectors.py	Thu Jul 08 16:30:19 2010 +0200
@@ -202,6 +202,7 @@
 from logilab.common.interface import implements as implements_iface
 
 from yams import BASE_TYPES
+from rql.nodes import Function
 
 from cubicweb import (Unauthorized, NoSelectableObject, NotAnEntity,
                       CW_EVENT_MANAGER, role)
@@ -588,12 +589,17 @@
 @lltrace
 def sorted_rset(cls, req, rset=None, **kwargs):
     """Return 1 for sorted result set (e.g. from an RQL query containing an
-    :ref:ORDERBY clause.
+    :ref:ORDERBY clause), with exception that it will return 0 if the rset is
+    'ORDERBY FTIRANK(VAR)' (eg sorted by rank value of the has_text index).
     """
     if rset is None:
         return 0
-    rqlst = rset.syntax_tree()
-    if len(rqlst.children) > 1 or not rqlst.children[0].orderby:
+    selects = rset.syntax_tree().children
+    if (len(selects) > 1 or
+        not selects[0].orderby or
+        (isinstance(selects[0].orderby[0].term, Function) and
+         selects[0].orderby[0].term.name == 'FTIRANK')
+        ):
         return 0
     return 2
 
--- a/server/msplanner.py	Thu Jul 08 16:29:51 2010 +0200
+++ b/server/msplanner.py	Thu Jul 08 16:30:19 2010 +0200
@@ -1155,6 +1155,7 @@
                 sunion = Union()
                 for select in queries:
                     sunion.append(select)
+                    self.rqlhelper.annotate(select)
                 if temptable:
                     steps.append(FetchStep(plan, sunion, sources, temptable, True, inputmap))
                 else:
--- a/server/serverconfig.py	Thu Jul 08 16:29:51 2010 +0200
+++ b/server/serverconfig.py	Thu Jul 08 16:30:19 2010 +0200
@@ -15,9 +15,8 @@
 #
 # You should have received a copy of the GNU Lesser General Public License along
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
-"""server.serverconfig definition
+"""server.serverconfig definition"""
 
-"""
 __docformat__ = "restructuredtext en"
 
 from os.path import join, exists
--- a/utils.py	Thu Jul 08 16:29:51 2010 +0200
+++ b/utils.py	Thu Jul 08 16:30:19 2010 +0200
@@ -327,12 +327,13 @@
 
 try:
     # may not be there if cubicweb-web not installed
-    if sys.version_info < (2,6):
+    if sys.version_info < (2, 6):
         import simplejson as json
     else:
         import json
 except ImportError:
-    pass
+    json_dumps = None
+
 else:
 
     class CubicWebJsonEncoder(json.JSONEncoder):
@@ -360,6 +361,9 @@
                 # just return None in those cases.
                 return None
 
+    def json_dumps(value):
+        return json.dumps(value, cls=CubicWebJsonEncoder)
+
 
 @deprecated('[3.7] merge_dicts is deprecated')
 def merge_dicts(dict1, dict2):
--- a/web/__init__.py	Thu Jul 08 16:29:51 2010 +0200
+++ b/web/__init__.py	Thu Jul 08 16:30:19 2010 +0200
@@ -22,20 +22,14 @@
 __docformat__ = "restructuredtext en"
 _ = unicode
 
-import sys
-if sys.version_info < (2,6):
-    import simplejson as json
-else:
-    import json
-
-dumps = json.dumps
-
 from urllib import quote as urlquote
 
 from logilab.common.deprecation import deprecated
 
 from cubicweb.web._exceptions import *
-from cubicweb.utils import CubicWebJsonEncoder
+from cubicweb.utils import json_dumps
+
+dumps = deprecated('[3.9] use cubicweb.utils.json_dumps instead of dumps')(json_dumps)
 
 INTERNAL_FIELD_VALUE = '__cubicweb_internal_field__'
 
@@ -64,9 +58,6 @@
 FACETTES = set()
 
 
-def json_dumps(value):
-    return dumps(value, cls=CubicWebJsonEncoder)
-
 def jsonize(function):
     def newfunc(*args, **kwargs):
         value = function(*args, **kwargs)
--- a/web/_exceptions.py	Thu Jul 08 16:29:51 2010 +0200
+++ b/web/_exceptions.py	Thu Jul 08 16:30:19 2010 +0200
@@ -16,12 +16,12 @@
 #
 # You should have received a copy of the GNU Lesser General Public License along
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
-"""exceptions used in the core of the CubicWeb web application
+"""exceptions used in the core of the CubicWeb web application"""
 
-"""
 __docformat__ = "restructuredtext en"
 
 from cubicweb._exceptions import *
+from cubicweb.utils import json_dumps
 
 class PublishException(CubicWebException):
     """base class for publishing related exception"""
@@ -66,8 +66,7 @@
         self.reason = reason
 
     def dumps(self):
-        from cubicweb.web import json
-        return json.dumps({'reason': self.reason})
+        return json_dumps({'reason': self.reason})
 
 class LogOut(PublishException):
     """raised to ask for deauthentication of a logged in user"""
--- a/web/component.py	Thu Jul 08 16:29:51 2010 +0200
+++ b/web/component.py	Thu Jul 08 16:30:19 2010 +0200
@@ -26,7 +26,7 @@
 from logilab.mtconverter import xml_escape
 
 from cubicweb import role
-from cubicweb.web import json
+from cubicweb.utils import json_dumps
 from cubicweb.view import Component
 from cubicweb.selectors import (
     paginated_rset, one_line_rset, primary_view, match_context_prop,
@@ -146,9 +146,9 @@
             rql = params.pop('rql', self.cw_rset.printable_rql())
             # latest 'true' used for 'swap' mode
             url = 'javascript: replacePageChunk(%s, %s, %s, %s, true)' % (
-                json.dumps(params.get('divid', 'pageContent')),
-                json.dumps(rql), json.dumps(params.pop('vid', None)),
-                json.dumps(params))
+                json_dumps(params.get('divid', 'pageContent')),
+                json_dumps(rql), json_dumps(params.pop('vid', None)),
+                json_dumps(params))
         else:
             url = self._cw.build_url(path, **params)
         return url
Binary file web/data/boxHeader.png has changed
--- a/web/data/cubicweb.ajax.js	Thu Jul 08 16:29:51 2010 +0200
+++ b/web/data/cubicweb.ajax.js	Thu Jul 08 16:30:19 2010 +0200
@@ -241,7 +241,7 @@
         $(node).removeClass("hidden");
         if (mode == 'swap') {
             var origId = node.id;
-            node = swapDOM(node, domnode);
+            node = cw.swapDOM(node, domnode);
             if (!node.id) {
                 node.id = origId;
             }
@@ -255,16 +255,16 @@
             callback = callback.apply(this, [domnode]);
         }
     });
+    d.addErrback(remoteCallFailed);
     if (cursor) {
         d.addCallback(resetCursor);
         d.addErrback(resetCursor);
-        d.addErrback(remoteCallFailed);
     }
     return d;
 }
 
 /**
- * .. function:: loadRemote(url, form, reqtype='GET', async=true)
+ * .. function:: loadRemote(url, form, reqtype='GET', sync=false)
  *
  * Asynchronously (unless `async` argument is set to false) load an url or path
  * and return a deferred whose callbacks args are decoded according to the
--- a/web/data/cubicweb.css	Thu Jul 08 16:29:51 2010 +0200
+++ b/web/data/cubicweb.css	Thu Jul 08 16:30:19 2010 +0200
@@ -322,7 +322,7 @@
   overflow: hidden;
   font-weight: bold;
   color: #fff;
-  background: %(boxTitleBgColor)s url("header.png") repeat-x 50% 50%;
+  background: %(boxTitleBg)s;
 }
 
 div.boxTitle span,
@@ -333,7 +333,7 @@
 
 div.searchBoxFrame div.boxTitle,
 div.greyBoxFrame div.boxTitle {
-  background: %(actionBoxTitleBgColor)s url("actionBoxHeader.png") repeat-x 50% 50% ;
+  background: %(actionBoxTitleBg)s;
 }
 
 div.sideBoxTitle span,
@@ -379,7 +379,7 @@
 }
 
 div.sideBoxTitle {
-  background: %(actionBoxTitleBgColor)s;
+  background: %(actionBoxTitleBg)s;
   display: block;
   font-weight: bold;
 }
@@ -400,11 +400,11 @@
 
 div.sideBoxBody {
   padding: 0.2em 5px;
-  background: %(sideBoxBodyBgColor)s;
+  background: %(sideBoxBodyBg)s;
 }
 
 div.sideBoxBody a {
-  color: %(sideBoxColor)s;
+  color: %(sideBoxBodyColor)s;
 }
 
 div.sideBoxBody a:hover {
@@ -804,7 +804,7 @@
 ul.boxListing a:hover,
 ul.boxListing ul li a:hover {
   text-decoration: none;
-  background: %(sideBoxBodyBgColor)s;
+  background: %(sideBoxBodyBg)s;
 }
 
 ul.boxListing ul li a:hover{
--- a/web/data/cubicweb.edition.js	Thu Jul 08 16:29:51 2010 +0200
+++ b/web/data/cubicweb.edition.js	Thu Jul 08 16:30:19 2010 +0200
@@ -48,7 +48,7 @@
         if ($.inArray(tagName, inputTypes)) {
             if (jQuery(elem).attr('tabindex') != null) {
                 tabindex += 1;
-		jQuery(elem).attr('tabindex', tabindex);
+                jQuery(elem).attr('tabindex', tabindex);
             }
             return null;
         }
@@ -170,7 +170,8 @@
     // add hidden parameter
     var entityForm = jQuery('#entityForm');
     var oid = optionNode.id.substring(2); // option id is prefixed by "id"
-    remoteExec('add_pending_inserts', [oid.split(':')]);
+    loadRemote('json', ajaxFuncArgs('add_pending_inserts', null,
+                                    [oid.split(':')]), 'GET', true);
     var selectNode = optionNode.parentNode;
     // remove option node
     selectNode.removeChild(optionNode);
@@ -208,7 +209,8 @@
         }
     }
     elementId = elementId.substring(2, elementId.length);
-    remoteExec('remove_pending_insert', elementId.split(':'));
+    loadRemote('json', ajaxFuncArgs('remove_pending_inserts', null,
+                                    elementId.split(':')), 'GET', true);
 }
 
 /**
@@ -272,7 +274,7 @@
 
 function selectForAssociation(tripletIdsString, originalEid) {
     var tripletlist = $.map(tripletIdsString.split('-'),
-			    function(x) { return x.split(':');});
+			    function(x) { return [x.split(':')] ;});
     var d = loadRemote('json', ajaxFuncArgs('add_pending_inserts', null, tripletlist));
     d.addCallback(function() {
         var args = {
@@ -337,8 +339,8 @@
  */
 function removeInlineForm(peid, rtype, role, eid, showaddnewlink) {
     cw.jqNode(['div', peid, rtype, eid].join('-')).slideUp('fast', function() {
-	    $(this).remove();
-	    updateInlinedEntitiesCounters(rtype, role);
+            $(this).remove();
+            updateInlinedEntitiesCounters(rtype, role);
     });
     if (showaddnewlink) {
         toggleVisibility(showaddnewlink);
@@ -469,10 +471,10 @@
     var errmsg;
     // Unknown structure
     if ( !cw.utils.isArrayLike(descr) || descr.length != 2 ) {
-	errmsg = descr;
+        errmsg = descr;
     } else {
-	_displayValidationerrors(formid, descr[0], descr[1]);
-	errmsg = _('please correct errors below');
+        _displayValidationerrors(formid, descr[0], descr[1]);
+        errmsg = _('please correct errors below');
     }
     updateMessage(errmsg);
     // ensure the browser does not scroll down
--- a/web/data/cubicweb.facets.js	Thu Jul 08 16:29:51 2010 +0200
+++ b/web/data/cubicweb.facets.js	Thu Jul 08 16:30:19 2010 +0200
@@ -1,10 +1,15 @@
-/**
+/** filter form, aka facets, javascript functions
+ *
  *  :organization: Logilab
  *  :copyright: 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
  *  :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
  */
 
-//============= filter form functions ========================================//
+var SELECTED_IMG = baseuri() + "data/black-check.png";
+var UNSELECTED_IMG = baseuri() + "data/no-check-no-border.png";
+var UNSELECTED_BORDER_IMG = baseuri() + "data/black-uncheck.png";
+
+
 function copyParam(origparams, newparams, param) {
     var index = jQuery.inArray(param, origparams[0]);
     if (index > - 1) {
@@ -12,31 +17,33 @@
     }
 }
 
-function facetFormContent(form) {
+
+function facetFormContent($form) {
     var names = [];
     var values = [];
-    jQuery(form).find('.facet').each(function() {
+    $form.find('.facet').each(function() {
         var facetName = jQuery(this).find('.facetTitle').attr('cubicweb:facetName');
         var facetValues = jQuery(this).find('.facetValueSelected').each(function(x) {
             names.push(facetName);
             values.push(this.getAttribute('cubicweb:value'));
         });
     });
-    jQuery(form).find('input').each(function() {
+    $form.find('input').each(function() {
         names.push(this.name);
         values.push(this.value);
     });
-    jQuery(form).find('select option[selected]').each(function() {
+    $form.find('select option[selected]').each(function() {
         names.push(this.parentNode.name);
         values.push(this.value);
     });
     return [names, values];
 }
 
+
 function buildRQL(divid, vid, paginate, vidargs) {
     jQuery(CubicWeb).trigger('facets-content-loading', [divid, vid, paginate, vidargs]);
-    var form = getNode(divid + 'Form');
-    var zipped = facetFormContent(form);
+    var $form = $('#' + divid + 'Form');
+    var zipped = facetFormContent($form);
     zipped[0].push('facetargs');
     zipped[1].push(vidargs);
     var d = loadRemote('json', ajaxFuncArgs('filter_build_rql', null, zipped[0], zipped[1]));
@@ -78,8 +85,8 @@
         });
         if (paginate) {
             // FIXME the edit box might not be displayed in which case we don't
-            // know where to put the potential new one, just skip this case
-            // for now
+            // know where to put the potential new one, just skip this case for
+            // now
             var $node = jQuery('#edit_box');
             if ($node.length) {
                 $node.loadxhtml('json', ajaxFuncArgs('render', {
@@ -116,9 +123,6 @@
     });
 }
 
-var SELECTED_IMG = baseuri() + "data/black-check.png";
-var UNSELECTED_IMG = baseuri() + "data/no-check-no-border.png";
-var UNSELECTED_BORDER_IMG = baseuri() + "data/black-uncheck.png";
 
 function initFacetBoxEvents(root) {
     // facetargs : (divid, vid, paginate, extraargs)
@@ -206,6 +210,7 @@
     });
 }
 
+
 // trigger this function on document ready event if you provide some kind of
 // persistent search (eg crih)
 function reorderFacetsItems(root) {
@@ -233,10 +238,11 @@
     });
 }
 
-// we need to differenciate cases where initFacetBoxEvents is called
-// with one argument or without any argument. If we use `initFacetBoxEvents`
-// as the direct callback on the jQuery.ready event, jQuery will pass some argument
-// of his, so we use this small anonymous function instead.
+
+// we need to differenciate cases where initFacetBoxEvents is called with one
+// argument or without any argument. If we use `initFacetBoxEvents` as the
+// direct callback on the jQuery.ready event, jQuery will pass some argument of
+// his, so we use this small anonymous function instead.
 jQuery(document).ready(function() {
     initFacetBoxEvents();
 });
--- a/web/data/uiprops.py	Thu Jul 08 16:29:51 2010 +0200
+++ b/web/data/uiprops.py	Thu Jul 08 16:30:19 2010 +0200
@@ -2,7 +2,6 @@
 
 # CSS stylesheets to include systematically in HTML headers
 # use the following line if you *need* to keep the old stylesheet
-#STYLESHEETS =       [data('cubicweb.old.css')]
 STYLESHEETS =       [data('cubicweb.reset.css'),
                      data('cubicweb.css'), ]
 STYLESHEETS_IE =    [data('cubicweb.ie.css')]
@@ -66,7 +65,7 @@
 defaultFontFamily = "'Bitstream Vera Sans','Lucida Grande','Lucida Sans Unicode','Geneva','Verdana',sans-serif"
 defaultSize = '12px'
 defaultLineHeight = '1.5'
-defaultLineHeightEm = defaultLineHeight + 'em'
+defaultLineHeightEm = lazystr('%(defaultLineHeight)sem')
 baseRhythmBg = 'rhythm18.png'
 
 inputHeight = '1.3em'
@@ -82,7 +81,7 @@
 h1Padding = '0 0 0.14em 0 '
 h1Margin = '0.8em 0 0.5em'
 h1Color = '#000'
-h1BorderBottomStyle = '0.06em solid %s' % h1Color
+h1BorderBottomStyle = lazystr('0.06em solid %(h1Color)s')
 
 h2FontSize = '1.33333em'
 h2Padding = '0.4em 0 0.35em 0'
@@ -94,7 +93,7 @@
 
 # links
 aColor = '#e6820e'
-aActiveColor = aVisitedColor = aLinkColor = aColor
+aActiveColor = aVisitedColor = aLinkColor = lazystr('%(aColor)s')
 
 
 # page frame
@@ -105,13 +104,15 @@
 pageMinHeight = '800px'
 
 # boxes
-boxTitleBgColor = headerBgColor
+boxTitleBg = lazystr('%(headerBgColor)s url("boxHeader.png") repeat-x 50%% 50%%')
 boxBodyBgColor = '#efefde'
 
 # action, search, sideBoxes
 actionBoxTitleBgColor = '#cfceb7'
+actionBoxTitleBg = lazystr('%(actionBoxTitleBgColor)s url("actionBoxHeader.png") repeat-x 50%% 50%%')
 sideBoxBodyBgColor = '#f8f8ee'
-sideBoxColor = '#555544'
+sideBoxBodyBg = lazystr('%(sideBoxBodyBgColor)s')
+sideBoxBodyColor = '#555544'
 
 # table listing & co
 listingBorderColor = '#ccc'
@@ -122,7 +123,7 @@
 bulletDownImg = 'url("puce_down.png") 98% 6px no-repeat'
 
 #forms
-formHeaderBgColor = listingHeaderBgColor
+formHeaderBgColor = lazystr('%(listingHeaderBgColor)s')
 helperColor = '#555'
 
 # button
--- a/web/propertysheet.py	Thu Jul 08 16:29:51 2010 +0200
+++ b/web/propertysheet.py	Thu Jul 08 16:30:19 2010 +0200
@@ -23,6 +23,13 @@
 import os
 import os.path as osp
 
+class lazystr(object):
+    def __init__(self, string, context):
+        self.string = string
+        self.context = context
+    def __str__(self):
+        return self.string % self.context
+
 
 class PropertySheet(dict):
     def __init__(self, cache_directory, **context):
@@ -30,8 +37,12 @@
         self.context = context
         self.reset()
         context['sheet'] = self
+        context['lazystr'] = self.lazystr
         self._percent_rgx = re.compile('%(?!\()')
 
+    def lazystr(self, str):
+        return lazystr(str, self)
+
     def reset(self):
         self.clear()
         self._ordered_propfiles = []
--- a/web/request.py	Thu Jul 08 16:29:51 2010 +0200
+++ b/web/request.py	Thu Jul 08 16:30:19 2010 +0200
@@ -37,14 +37,12 @@
 from cubicweb.dbapi import DBAPIRequest
 from cubicweb.mail import header
 from cubicweb.uilib import remove_html_tags
-from cubicweb.utils import SizeConstrainedList, HTMLHead, make_uid
+from cubicweb.utils import SizeConstrainedList, HTMLHead, make_uid, json_dumps
 from cubicweb.view import STRICT_DOCTYPE, TRANSITIONAL_DOCTYPE_NOEXT
 from cubicweb.web import (INTERNAL_FIELD_VALUE, LOGGER, NothingToEdit,
-                          RequestError, StatusResponse, json)
+                          RequestError, StatusResponse)
 from cubicweb.web.http_headers import Headers
 
-dumps = json.dumps
-
 _MARKER = object()
 
 
@@ -358,7 +356,7 @@
         """
         self.add_js('cubicweb.ajax.js')
         cbname = self.register_onetime_callback(cb, *args)
-        msg = dumps(msg or '')
+        msg = json_dumps(msg or '')
         return "javascript:userCallbackThenReloadPage('%s', %s)" % (
             cbname, msg)
 
@@ -592,7 +590,7 @@
         extraparams.setdefault('fname', 'view')
         url = self.build_url('json', **extraparams)
         return "javascript: $('#%s').loadxhtml(%s, null, 'get', '%s'); noop()" % (
-                nodeid, dumps(url), replacemode)
+                nodeid, json_dumps(url), replacemode)
 
     # urls/path management ####################################################
 
--- a/web/test/data/sheet1.py	Thu Jul 08 16:29:51 2010 +0200
+++ b/web/test/data/sheet1.py	Thu Jul 08 16:30:19 2010 +0200
@@ -1,3 +1,4 @@
 bgcolor = '#000000'
 stylesheets = ['%s/cubicweb.css' % datadir_url]
 logo = '%s/logo.png' % datadir_url
+lazy = lazystr('%(bgcolor)s')
--- a/web/test/unittest_propertysheet.py	Thu Jul 08 16:29:51 2010 +0200
+++ b/web/test/unittest_propertysheet.py	Thu Jul 08 16:30:19 2010 +0200
@@ -27,6 +27,10 @@
         # defined by sheet1, extended by sheet2
         self.assertEquals(ps['stylesheets'], ['http://cwtest.com/cubicweb.css',
                                               'http://cwtest.com/mycube.css'])
+        # lazy string defined by sheet1
+        self.assertIsInstance(ps['lazy'], lazystr)
+        self.assertEquals(str(ps['lazy']), '#FFFFFF')
+        # test compilation
         self.assertEquals(ps.compile('a {bgcolor: %(bgcolor)s; size: 1%;}'),
                           'a {bgcolor: #FFFFFF; size: 1%;}')
         self.assertEquals(ps.process_resource(DATADIR, 'pouet.css'),
--- a/web/test/unittest_views_basecontrollers.py	Thu Jul 08 16:29:51 2010 +0200
+++ b/web/test/unittest_views_basecontrollers.py	Thu Jul 08 16:30:19 2010 +0200
@@ -15,17 +15,16 @@
 #
 # You should have received a copy of the GNU Lesser General Public License along
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
-"""cubicweb.web.views.basecontrollers unit tests
-
-"""
+"""cubicweb.web.views.basecontrollers unit tests"""
 
 from logilab.common.testlib import unittest_main, mock_object
 
 from cubicweb import Binary, NoSelectableObject, ValidationError
 from cubicweb.view import STRICT_DOCTYPE
 from cubicweb.devtools.testlib import CubicWebTC
+from cubicweb.utils import json_dumps
 from cubicweb.uilib import rql_for_eid
-from cubicweb.web import INTERNAL_FIELD_VALUE, Redirect, RequestError, json
+from cubicweb.web import INTERNAL_FIELD_VALUE, Redirect, RequestError
 from cubicweb.entities.authobjs import CWUser
 from cubicweb.web.views.autoform import get_pending_inserts, get_pending_deletes
 u = unicode
@@ -562,7 +561,7 @@
 #         rql = 'Any T,N WHERE T is Tag, T name N'
 #         ctrl = self.ctrl(self.request(mode='json', rql=rql, pageid='123'))
 #         self.assertEquals(ctrl.publish(),
-#                           json.dumps(self.execute(rql).rows))
+#                           json_dumps(self.execute(rql).rows))
 
     def test_remote_add_existing_tag(self):
         self.remote_call('tag_entity', self.john.eid, ['python'])
@@ -643,14 +642,14 @@
     # silly tests
     def test_external_resource(self):
         self.assertEquals(self.remote_call('external_resource', 'RSS_LOGO')[0],
-                          json.dumps(self.config.uiprops['RSS_LOGO']))
+                          json_dumps(self.config.uiprops['RSS_LOGO']))
     def test_i18n(self):
         self.assertEquals(self.remote_call('i18n', ['bimboom'])[0],
-                          json.dumps(['bimboom']))
+                          json_dumps(['bimboom']))
 
     def test_format_date(self):
         self.assertEquals(self.remote_call('format_date', '2007-01-01 12:00:00')[0],
-                          json.dumps('2007/01/01'))
+                          json_dumps('2007/01/01'))
 
 
 
--- a/web/test/unittest_views_baseviews.py	Thu Jul 08 16:29:51 2010 +0200
+++ b/web/test/unittest_views_baseviews.py	Thu Jul 08 16:30:19 2010 +0200
@@ -15,21 +15,17 @@
 #
 # You should have received a copy of the GNU Lesser General Public License along
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
-"""
 
-"""
 from logilab.common.testlib import unittest_main
 from logilab.mtconverter import html_unescape
 
 from cubicweb.devtools.testlib import CubicWebTC
-
+from cubicweb.utils import json
 from cubicweb.web.htmlwidgets import TableWidget
 from cubicweb.web.views import vid_from_rset
-from cubicweb.web import json
-loads = json.loads
 
 def loadjson(value):
-    return loads(html_unescape(value))
+    return json.loads(html_unescape(value))
 
 class VidFromRsetTC(CubicWebTC):
 
--- a/web/views/autoform.py	Thu Jul 08 16:29:51 2010 +0200
+++ b/web/views/autoform.py	Thu Jul 08 16:30:19 2010 +0200
@@ -134,8 +134,9 @@
 from cubicweb.selectors import (
     match_kwargs, match_form_params, non_final_entity,
     specified_etype_implements)
-from cubicweb.web import stdmsgs, uicfg, eid_param, dumps, \
-     form as f, formwidgets as fw, formfields as ff
+from cubicweb.utils import json_dumps
+from cubicweb.web import (stdmsgs, uicfg, eid_param,
+                          form as f, formwidgets as fw, formfields as ff)
 from cubicweb.web.views import forms
 
 _AFS = uicfg.autoform_section
@@ -374,7 +375,7 @@
     entities
     """
     js = u"javascript: togglePendingDelete('%s', %s);" % (
-        nodeid, xml_escape(dumps(eid)))
+        nodeid, xml_escape(json_dumps(eid)))
     return u'[<a class="handle" href="%s" id="handle%s">%s</a>]' % (
         js, nodeid, label)
 
@@ -475,7 +476,7 @@
         w(u'<th class="labelCol">')
         w(u'<select id="relationSelector_%s" tabindex="%s" '
           'onchange="javascript:showMatchingSelect(this.options[this.selectedIndex].value,%s);">'
-          % (eid, req.next_tabindex(), xml_escape(dumps(eid))))
+          % (eid, req.next_tabindex(), xml_escape(json_dumps(eid))))
         w(u'<option value="">%s</option>' % _('select a relation'))
         for i18nrtype, rschema, role in field.relations:
             # more entities to link to
@@ -599,7 +600,7 @@
   </select>
 </div>
 """ % (hidden and 'hidden' or '', divid, selectid,
-       xml_escape(dumps(entity.eid)), is_cell and 'true' or 'null', relname,
+       xml_escape(json_dumps(entity.eid)), is_cell and 'true' or 'null', relname,
        '\n'.join(options))
 
     def _get_select_options(self, entity, rschema, role):
--- a/web/views/basecontrollers.py	Thu Jul 08 16:29:51 2010 +0200
+++ b/web/views/basecontrollers.py	Thu Jul 08 16:30:19 2010 +0200
@@ -26,11 +26,10 @@
 
 from cubicweb import (NoSelectableObject, ObjectNotFound, ValidationError,
                       AuthenticationError, typed_eid)
-from cubicweb.utils import CubicWebJsonEncoder
+from cubicweb.utils import json, json_dumps
 from cubicweb.selectors import authenticated_user, anonymous_user, match_form_params
 from cubicweb.mail import format_mail
-from cubicweb.web import (Redirect, RemoteCallFailed, DirectResponse,
-                          json, json_dumps)
+from cubicweb.web import Redirect, RemoteCallFailed, DirectResponse
 from cubicweb.web.controller import Controller
 from cubicweb.web.views import vid_from_rset, formrenderers
 
@@ -42,7 +41,7 @@
     HAS_SEARCH_RESTRICTION = False
 
 def jsonize(func):
-    """decorator to sets correct content_type and calls `json.dumps` on
+    """decorator to sets correct content_type and calls `json_dumps` on
     results
     """
     def wrapper(self, *args, **kwargs):
--- a/web/views/editforms.py	Thu Jul 08 16:29:51 2010 +0200
+++ b/web/views/editforms.py	Thu Jul 08 16:30:19 2010 +0200
@@ -33,7 +33,7 @@
                                 specified_etype_implements, is_instance, yes)
 from cubicweb.view import EntityView
 from cubicweb.schema import display_name
-from cubicweb.web import uicfg, stdmsgs, eid_param, dumps, \
+from cubicweb.web import uicfg, stdmsgs, eid_param, \
      formfields as ff, formwidgets as fw
 from cubicweb.web.form import FormViewMixIn, FieldNotFound
 from cubicweb.web.views import forms, reledit
--- a/web/views/facets.py	Thu Jul 08 16:29:51 2010 +0200
+++ b/web/views/facets.py	Thu Jul 08 16:30:19 2010 +0200
@@ -15,9 +15,8 @@
 #
 # You should have received a copy of the GNU Lesser General Public License along
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
-"""the facets box and some basic facets
+"""the facets box and some basic facets"""
 
-"""
 __docformat__ = "restructuredtext en"
 
 from logilab.mtconverter import xml_escape
@@ -25,7 +24,7 @@
 from cubicweb.appobject import objectify_selector
 from cubicweb.selectors import (non_final_entity, multi_lines_rset,
                                 match_context_prop, yes, relation_possible)
-from cubicweb.web import dumps
+from cubicweb.utils import json_dumps
 from cubicweb.web.box import BoxTemplate
 from cubicweb.web.facet import (AbstractFacet, FacetStringWidget, RelationFacet,
                                 prepare_facets_rqlst, filter_hiddens, _cleanup_rqlst,
@@ -102,7 +101,7 @@
             self.display_bookmark_link(rset)
         w = self.w
         w(u'<form method="post" id="%sForm" cubicweb:facetargs="%s" action="">'  % (
-            divid, xml_escape(dumps([divid, vid, paginate, self.facetargs()]))))
+            divid, xml_escape(json_dumps([divid, vid, paginate, self.facetargs()]))))
         w(u'<fieldset>')
         hiddens = {'facets': ','.join(wdg.facet.__regid__ for wdg in widgets),
                    'baserql': baserql}
--- a/web/views/formrenderers.py	Thu Jul 08 16:29:51 2010 +0200
+++ b/web/views/formrenderers.py	Thu Jul 08 16:30:19 2010 +0200
@@ -41,7 +41,8 @@
 from cubicweb import tags
 from cubicweb.appobject import AppObject
 from cubicweb.selectors import is_instance, yes
-from cubicweb.web import dumps, eid_param, formwidgets as fwdgs
+from cubicweb.utils import json_dumps
+from cubicweb.web import eid_param, formwidgets as fwdgs
 
 
 def checkbox(name, value, attrs='', checked=None):
@@ -359,7 +360,7 @@
             values = form.form_previous_values
             qeid = eid_param('eid', entity.eid)
             cbsetstate = "setCheckboxesState('eid', %s, 'checked')" % \
-                         xml_escape(dumps(entity.eid))
+                         xml_escape(json_dumps(entity.eid))
             w(u'<tr class="%s">' % (entity.cw_row % 2 and u'even' or u'odd'))
             # XXX turn this into a widget used on the eid field
             w(u'<td>%s</td>' % checkbox('eid', entity.eid,
--- a/web/views/igeocodable.py	Thu Jul 08 16:29:51 2010 +0200
+++ b/web/views/igeocodable.py	Thu Jul 08 16:30:19 2010 +0200
@@ -22,7 +22,7 @@
 from cubicweb.interfaces import IGeocodable
 from cubicweb.view import EntityView, EntityAdapter, implements_adapter_compat
 from cubicweb.selectors import implements, adaptable
-from cubicweb.web import json
+from cubicweb.utils import json_dumps
 
 class IGeocodableAdapter(EntityAdapter):
     """interface required by geocoding views such as gmap-view"""
@@ -82,7 +82,7 @@
             'center': center,
             'markers': markers,
             }
-        self.w(json.dumps(geodata))
+        self.w(json_dumps(geodata))
 
     def build_marker_data(self, entity, igeocodable, extraparams):
         return {'latitude': igeocodable.latitude,
--- a/web/views/navigation.py	Thu Jul 08 16:29:51 2010 +0200
+++ b/web/views/navigation.py	Thu Jul 08 16:30:19 2010 +0200
@@ -132,7 +132,7 @@
                 if rel is None:
                     continue
                 attrname = rel.r_type
-                if attrname == 'is':
+                if attrname in ('is', 'has_text'):
                     continue
                 if not rschema(attrname).final:
                     col = var.selected_index()
--- a/web/views/plots.py	Thu Jul 08 16:29:51 2010 +0200
+++ b/web/views/plots.py	Thu Jul 08 16:30:19 2010 +0200
@@ -23,10 +23,9 @@
 from logilab.common.date import datetime2ticks
 from logilab.mtconverter import xml_escape
 
-from cubicweb.utils import UStringIO
+from cubicweb.utils import UStringIO, json_dumps
 from cubicweb.appobject import objectify_selector
 from cubicweb.selectors import multi_columns_rset
-from cubicweb.web import dumps
 from cubicweb.web.views import baseviews
 
 @objectify_selector
@@ -107,7 +106,7 @@
         #     cf. function onPlotHover in cubicweb.flot.js
         if self.timemode:
             plot = [(datetime2ticks(x), y, datetime2ticks(x)) for x, y in plot]
-        return dumps(plot)
+        return json_dumps(plot)
 
     def _render(self, req, width=500, height=400):
         if req.ie_browser():
--- a/web/views/reledit.py	Thu Jul 08 16:29:51 2010 +0200
+++ b/web/views/reledit.py	Thu Jul 08 16:30:19 2010 +0200
@@ -1,10 +1,30 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# CubicWeb is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
+"""the 'reedit' feature (eg edit attribute/relation from primary view)
+"""
+
 import copy
 
 from logilab.mtconverter import xml_escape
 
 from cubicweb import neg_role
 from cubicweb.schema import display_name
-from cubicweb.utils import json
+from cubicweb.utils import json_dumps
 from cubicweb.selectors import non_final_entity, match_kwargs
 from cubicweb.view import EntityView
 from cubicweb.web import uicfg, stdmsgs
@@ -196,7 +216,7 @@
                     extradata=None):
         divid = self._build_divid(rtype, role, entity.eid)
         event_args = {'divid' : divid, 'eid' : entity.eid, 'rtype' : rtype, 'formid': formid,
-                      'reload' : json.dumps(reload), 'default_value' : default_value,
+                      'reload' : json_dumps(reload), 'default_value' : default_value,
                       'role' : role, 'vid' : u''}
         if extradata:
             event_args.update(extradata)
--- a/web/views/tableview.py	Thu Jul 08 16:29:51 2010 +0200
+++ b/web/views/tableview.py	Thu Jul 08 16:30:19 2010 +0200
@@ -16,17 +16,13 @@
 # You should have received a copy of the GNU Lesser General Public License along
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """generic table view, including filtering abilities"""
+
 __docformat__ = "restructuredtext en"
 
-try:
-    from json import dumps
-except ImportError:
-    from simplejson import dumps
-
 from logilab.mtconverter import xml_escape
 
 from cubicweb.selectors import nonempty_rset, match_form_params
-from cubicweb.utils import make_uid
+from cubicweb.utils import make_uid, json_dumps
 from cubicweb.view import EntityView, AnyRsetView
 from cubicweb import tags
 from cubicweb.uilib import toggle_action, limitsize, htmlescape
@@ -77,7 +73,7 @@
         # drop False / None values from vidargs
         vidargs = dict((k, v) for k, v in vidargs.iteritems() if v)
         w(u'<form method="post" cubicweb:facetargs="%s" action="">' %
-          xml_escape(dumps([divid, self.__regid__, False, vidargs])))
+          xml_escape(json_dumps([divid, self.__regid__, False, vidargs])))
         w(u'<fieldset id="%sForm" class="%s">' % (divid, hidden and 'hidden' or ''))
         w(u'<input type="hidden" name="divid" value="%s" />' % divid)
         w(u'<input type="hidden" name="fromformfilter" value="1" />')
@@ -197,7 +193,8 @@
         rql = params.pop('rql', self.cw_rset.printable_rql())
         # latest 'true' used for 'swap' mode
         return 'javascript: replacePageChunk(%s, %s, %s, %s, true)' % (
-            dumps(divid), dumps(rql), dumps(self.__regid__), dumps(params))
+            json_dumps(divid), json_dumps(rql), json_dumps(self.__regid__),
+            json_dumps(params))
 
     def show_hide_actions(self, divid, currentlydisplayed=False):
         showhide = u';'.join(toggle_action('%s%s' % (divid, what))[11:]
--- a/web/views/timeline.py	Thu Jul 08 16:29:51 2010 +0200
+++ b/web/views/timeline.py	Thu Jul 08 16:30:19 2010 +0200
@@ -26,7 +26,7 @@
 
 from cubicweb.selectors import adaptable
 from cubicweb.view import EntityView, StartupView
-from cubicweb.web import json
+from cubicweb.utils import json_dumps
 
 _ = unicode
 
@@ -52,7 +52,7 @@
                 events.append(event)
         timeline_data = {'dateTimeFormat': self.date_fmt,
                          'events': events}
-        self.w(json.dumps(timeline_data))
+        self.w(json_dumps(timeline_data))
 
     # FIXME: those properties should be defined by the entity class
     def onclick_url(self, entity):
--- a/web/views/treeview.py	Thu Jul 08 16:29:51 2010 +0200
+++ b/web/views/treeview.py	Thu Jul 08 16:30:19 2010 +0200
@@ -25,11 +25,10 @@
 
 from logilab.mtconverter import xml_escape
 
-from cubicweb.utils import make_uid
+from cubicweb.utils import make_uid, json
 from cubicweb.selectors import adaptable
 from cubicweb.view import EntityView
 from cubicweb.mixins import _done_init
-from cubicweb.web import json
 from cubicweb.web.views import baseviews
 
 def treecookiename(treeid):
--- a/web/webconfig.py	Thu Jul 08 16:29:51 2010 +0200
+++ b/web/webconfig.py	Thu Jul 08 16:30:19 2010 +0200
@@ -15,9 +15,8 @@
 #
 # You should have received a copy of the GNU Lesser General Public License along
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
-"""common web configuration for twisted/modpython instances
+"""web ui configuration for cubicweb instances"""
 
-"""
 __docformat__ = "restructuredtext en"
 _ = unicode
 
@@ -335,14 +334,18 @@
     def _build_ui_properties(self):
         # self.datadir_url[:-1] to remove trailing /
         from cubicweb.web.propertysheet import PropertySheet
+        cachedir = join(self.appdatahome, 'uicache')
+        self.check_writeable_uid_directory(cachedir)
         self.uiprops = PropertySheet(
-            join(self.appdatahome, 'uicache'),
+            cachedir,
             data=lambda x: self.datadir_url + x,
             datadir_url=self.datadir_url[:-1])
         self._init_uiprops(self.uiprops)
         if self['https-url']:
+            cachedir = join(self.appdatahome, 'uicachehttps')
+            self.check_writeable_uid_directory(cachedir)
             self.https_uiprops = PropertySheet(
-                join(self.appdatahome, 'uicache'),
+                cachedir,
                 data=lambda x: self.https_datadir_url + x,
                 datadir_url=self.https_datadir_url[:-1])
             self._init_uiprops(self.https_uiprops)