rename and move cw.RequestSessionMixIn to cw.req.RequestSessionBase; move some appobjects methods where they actually belong to
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Thu, 13 Aug 2009 09:30:03 +0200
changeset 2792 135580d15d42
parent 2791 7ef2b08fbe28
child 2794 54d18916374d
rename and move cw.RequestSessionMixIn to cw.req.RequestSessionBase; move some appobjects methods where they actually belong to
__init__.py
appobject.py
dbapi.py
devtools/fake.py
goa/db.py
req.py
rset.py
server/session.py
web/controller.py
web/request.py
web/views/basecontrollers.py
web/views/tableview.py
--- a/__init__.py	Thu Aug 13 09:23:22 2009 +0200
+++ b/__init__.py	Thu Aug 13 09:30:03 2009 +0200
@@ -7,7 +7,6 @@
 :license: Library General Public License version 2 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
-from cubicweb.__pkginfo__ import version as __version__
 
 import __builtin__
 # '_' is available in builtins to mark internationalized string but should
@@ -21,7 +20,6 @@
 from StringIO import StringIO
 from urllib import quote as urlquote, unquote as urlunquote
 
-from logilab.common.decorators import cached
 from logilab.common.logging_ext import set_log_methods
 
 if os.environ.get('APYCOT_ROOT'):
@@ -29,6 +27,8 @@
 else:
     logging.basicConfig()
 
+from cubicweb.__pkginfo__ import version as __version__
+
 
 set_log_methods(sys.modules[__name__], logging.getLogger('cubicweb'))
 
@@ -57,173 +57,6 @@
         StringIO.write(self, data)
 
 
-class RequestSessionMixIn(object):
-    """mixin class containing stuff shared by server session and web request
-    """
-    def __init__(self, vreg):
-        self.vreg = vreg
-        try:
-            encoding = vreg.property_value('ui.encoding')
-        except: # no vreg or property not registered
-            encoding = 'utf-8'
-        self.encoding = encoding
-        # cache result of execution for (rql expr / eids),
-        # should be emptied on commit/rollback of the server session / web
-        # connection
-        self.local_perm_cache = {}
-
-    def property_value(self, key):
-        if self.user:
-            return self.user.property_value(key)
-        return self.vreg.property_value(key)
-
-    def etype_rset(self, etype, size=1):
-        """return a fake result set for a particular entity type"""
-        from cubicweb.rset import ResultSet
-        rset = ResultSet([('A',)]*size, '%s X' % etype,
-                         description=[(etype,)]*size)
-        def get_entity(row, col=0, etype=etype, req=self, rset=rset):
-            return req.vreg.etype_class(etype)(req, rset, row, col)
-        rset.get_entity = get_entity
-        return self.decorate_rset(rset)
-
-    def eid_rset(self, eid, etype=None):
-        """return a result set for the given eid without doing actual query
-        (we have the eid, we can suppose it exists and user has access to the
-        entity)
-        """
-        from cubicweb.rset import ResultSet
-        eid = typed_eid(eid)
-        if etype is None:
-            etype = self.describe(eid)[0]
-        rset = ResultSet([(eid,)], 'Any X WHERE X eid %(x)s', {'x': eid},
-                         [(etype,)])
-        return self.decorate_rset(rset)
-
-    def empty_rset(self):
-        """return a result set for the given eid without doing actual query
-        (we have the eid, we can suppose it exists and user has access to the
-        entity)
-        """
-        from cubicweb.rset import ResultSet
-        return self.decorate_rset(ResultSet([], 'Any X WHERE X eid -1'))
-
-    def entity_from_eid(self, eid, etype=None):
-        try:
-            return self.entity_cache(eid)
-        except KeyError:
-            rset = self.eid_rset(eid, etype)
-            entity = rset.get_entity(0, 0)
-            self.set_entity_cache(entity)
-            return entity
-
-    def entity_cache(self, eid):
-        raise KeyError
-    def set_entity_cache(self, entity):
-        pass
-    # url generation methods ##################################################
-
-    def build_url(self, *args, **kwargs):
-        """return an absolute URL using params dictionary key/values as URL
-        parameters. Values are automatically URL quoted, and the
-        publishing method to use may be specified or will be guessed.
-        """
-        # use *args since we don't want first argument to be "anonymous" to
-        # avoid potential clash with kwargs
-        assert len(args) == 1, 'only 0 or 1 non-named-argument expected'
-        method = args[0]
-        base_url = kwargs.pop('base_url', None)
-        if base_url is None:
-            base_url = self.base_url()
-        if '_restpath' in kwargs:
-            assert method == 'view', method
-            path = kwargs.pop('_restpath')
-        else:
-            path = method
-        if not kwargs:
-            return u'%s%s' % (base_url, path)
-        return u'%s%s?%s' % (base_url, path, self.build_url_params(**kwargs))
-
-
-    def build_url_params(self, **kwargs):
-        """return encoded params to incorporate them in an URL"""
-        args = []
-        for param, values in kwargs.items():
-            if not isinstance(values, (list, tuple)):
-                values = (values,)
-            for value in values:
-                args.append(u'%s=%s' % (param, self.url_quote(value)))
-        return '&'.join(args)
-
-    def url_quote(self, value, safe=''):
-        """urllib.quote is not unicode safe, use this method to do the
-        necessary encoding / decoding. Also it's designed to quote each
-        part of a url path and so the '/' character will be encoded as well.
-        """
-        if isinstance(value, unicode):
-            quoted = urlquote(value.encode(self.encoding), safe=safe)
-            return unicode(quoted, self.encoding)
-        return urlquote(str(value), safe=safe)
-
-    def url_unquote(self, quoted):
-        """returns a unicode unquoted string
-
-        decoding is based on `self.encoding` which is the encoding
-        used in `url_quote`
-        """
-        if isinstance(quoted, unicode):
-            quoted = quoted.encode(self.encoding)
-        try:
-            return unicode(urlunquote(quoted), self.encoding)
-        except UnicodeDecodeError: # might occurs on manually typed URLs
-            return unicode(urlunquote(quoted), 'iso-8859-1')
-
-
-    # session's user related methods #####################################
-
-    @cached
-    def user_data(self):
-        """returns a dictionnary with this user's information"""
-        userinfo = {}
-        if self.is_internal_session:
-            userinfo['login'] = "cubicweb"
-            userinfo['name'] = "cubicweb"
-            userinfo['email'] = ""
-            return userinfo
-        user = self.actual_session().user
-        rql = "Any F,S,A where U eid %(x)s, U firstname F, U surname S, U primary_email E, E address A"
-        try:
-            firstname, lastname, email = self.execute(rql, {'x': user.eid}, 'x')[0]
-            if firstname is None and lastname is None:
-                userinfo['name'] = ''
-            else:
-                userinfo['name'] = ("%s %s" % (firstname, lastname))
-            userinfo['email'] = email
-        except IndexError:
-            userinfo['name'] = None
-            userinfo['email'] = None
-        userinfo['login'] = user.login
-        return userinfo
-
-    def is_internal_session(self):
-        """overrided on the server-side"""
-        return False
-
-    # abstract methods to override according to the web front-end #############
-
-    def base_url(self):
-        """return the root url of the instance"""
-        raise NotImplementedError
-
-    def decorate_rset(self, rset):
-        """add vreg/req (at least) attributes to the given result set """
-        raise NotImplementedError
-
-    def describe(self, eid):
-        """return a tuple (type, sourceuri, extid) for the entity with id <eid>"""
-        raise NotImplementedError
-
-
 # XXX 2.45 is allowing nicer entity type names, use this map for bw compat
 ETYPE_NAME_MAP = {# 3.2 migration
                   'ECache': 'CWCache',
--- a/appobject.py	Thu Aug 13 09:23:22 2009 +0200
+++ b/appobject.py	Thu Aug 13 09:30:03 2009 +0200
@@ -11,17 +11,14 @@
 
 import types
 from logging import getLogger
-from datetime import datetime, timedelta, time
+from datetime import datetime, timedelta
 
 from logilab.common.decorators import classproperty
 from logilab.common.deprecation import deprecated
 from logilab.common.logging_ext import set_log_methods
 
-from rql.nodes import VariableRef, SubQuery
-from rql.stmts import Union, Select
-
 from cubicweb import Unauthorized, NoSelectableObject
-from cubicweb.utils import UStringIO, ustrftime, strptime, todate, todatetime
+from cubicweb.utils import UStringIO
 
 ONESECOND = timedelta(0, 1, 0)
 CACHE_REGISTRY = {}
@@ -293,8 +290,6 @@
         """
         cls.build___select__()
         cls.vreg = registry.vreg
-        cls.schema = registry.schema
-        cls.config = registry.config
         cls.register_properties()
         return cls
 
@@ -302,7 +297,7 @@
     def vreg_initialization_completed(cls):
         pass
 
-    # Eproperties definition:
+    # properties definition:
     # key: id of the property (the actual CWProperty key is build using
     #      <registry name>.<obj id>.<property id>
     # value: tuple (property type, vocabfunc, default value, property description)
@@ -326,15 +321,6 @@
     def propkey(cls, propid):
         return '%s.%s.%s' % (cls.__registry__, cls.id, propid)
 
-    @classproperty
-    @deprecated('[3.4] use __select__ and & or | operators')
-    def __selectors__(cls):
-        selector = cls.__select__
-        if isinstance(selector, AndSelector):
-            return tuple(selector.selectors)
-        if not isinstance(selector, tuple):
-            selector = (selector,)
-        return selector
 
     def __init__(self, req=None, rset=None, row=None, col=None, **extra):
         super(AppObject, self).__init__()
@@ -369,64 +355,12 @@
         assert self.req
         return self.req.property_value(self.propkey(propid))
 
-    def limited_rql(self):
-        """return a printable rql for the result set associated to the object,
-        with limit/offset correctly set according to maximum page size and
-        currently displayed page when necessary
-        """
-        # try to get page boundaries from the navigation component
-        # XXX we should probably not have a ref to this component here (eg in
-        #     cubicweb.common)
-        nav = self.vreg['components'].select_or_none('navigation', self.req,
-                                                     rset=self.rset)
-        if nav:
-            start, stop = nav.page_boundaries()
-            rql = self._limit_offset_rql(stop - start, start)
-        # result set may have be limited manually in which case navigation won't
-        # apply
-        elif self.rset.limited:
-            rql = self._limit_offset_rql(*self.rset.limited)
-        # navigation component doesn't apply and rset has not been limited, no
-        # need to limit query
-        else:
-            rql = self.rset.printable_rql()
-        return rql
-
-    def _limit_offset_rql(self, limit, offset):
-        rqlst = self.rset.syntax_tree()
-        if len(rqlst.children) == 1:
-            select = rqlst.children[0]
-            olimit, ooffset = select.limit, select.offset
-            select.limit, select.offset = limit, offset
-            rql = rqlst.as_string(kwargs=self.rset.args)
-            # restore original limit/offset
-            select.limit, select.offset = olimit, ooffset
-        else:
-            newselect = Select()
-            newselect.limit = limit
-            newselect.offset = offset
-            aliases = [VariableRef(newselect.get_variable(vref.name, i))
-                       for i, vref in enumerate(rqlst.selection)]
-            newselect.set_with([SubQuery(aliases, rqlst)], check=False)
-            newunion = Union()
-            newunion.append(newselect)
-            rql = rqlst.as_string(kwargs=self.rset.args)
-            rqlst.parent = None
-        return rql
-
     def view(self, __vid, rset=None, __fallback_oid=None, __registry='views',
              **kwargs):
         """shortcut to self.vreg.view method avoiding to pass self.req"""
         return self.vreg[__registry].render(__vid, self.req, __fallback_oid,
                                             rset=rset, **kwargs)
 
-    def initialize_varmaker(self):
-        varmaker = self.req.get_page_data('rql_varmaker')
-        if varmaker is None:
-            varmaker = self.req.varmaker
-            self.req.set_page_data('rql_varmaker', varmaker)
-        self.varmaker = varmaker
-
     # url generation methods ##################################################
 
     controller = 'view'
@@ -452,42 +386,6 @@
                 method = self.req.relative_path(includeparams=False) or 'view'
         return self.req.build_url(method, **kwargs)
 
-    # various resources accessors #############################################
-
-    @deprecated('[3.5] use self.rset.get_entity(row,col) instead')
-    def entity(self, row, col=0):
-        """short cut to get an entity instance for a particular row/column
-        (col default to 0)
-        """
-        return self.rset.get_entity(row, col)
-
-    def complete_entity(self, row, col=0, skip_bytes=True):
-        """short cut to get an completed entity instance for a particular
-        row (all instance's attributes have been fetched)
-        """
-        entity = self.rset.get_entity(row, col)
-        entity.complete(skip_bytes=skip_bytes)
-        return entity
-
-    def user_rql_callback(self, args, msg=None):
-        """register a user callback to execute some rql query and return an url
-        to call it ready to be inserted in html
-        """
-        def rqlexec(req, rql, args=None, key=None):
-            req.execute(rql, args, key)
-        return self.user_callback(rqlexec, args, msg)
-
-    def user_callback(self, cb, args, msg=None, nonify=False):
-        """register the given user callback and return an url to call it ready to be
-        inserted in html
-        """
-        from simplejson import dumps
-        self.req.add_js('cubicweb.ajax.js')
-        cbname = self.req.register_onetime_callback(cb, *args)
-        msg = dumps(msg or '')
-        return "javascript:userCallbackThenReloadPage('%s', %s)" % (
-            cbname, msg)
-
     # formating methods #######################################################
 
     def tal_render(self, template, variables):
@@ -503,70 +401,66 @@
         template.expand(context, output)
         return output.getvalue()
 
+    # deprecated ###############################################################
+
+    @classproperty
+    @deprecated('[3.4] use __select__ and & or | operators')
+    def __selectors__(cls):
+        selector = cls.__select__
+        if isinstance(selector, AndSelector):
+            return tuple(selector.selectors)
+        if not isinstance(selector, tuple):
+            selector = (selector,)
+        return selector
+
+    @classmethod
+    @deprecated('[3.5] use vreg.schema')
+    def schema(cls):
+        return cls.vreg.schema
+
+    @classmethod
+    @deprecated('[3.5] use vreg.config')
+    def schema(cls):
+        return cls.vreg.config
+
+    @deprecated('[3.5] use req.varmaker')
+    def initialize_varmaker(self):
+        self.varmaker = self.req.varmaker
+
+    @deprecated('[3.5] use rset.limited_rql')
+    def limited_rql(self):
+        return self.rset.limited_rql()
+
+    @deprecated('[3.5] use self.rset.complete_entity(row,col) instead')
+    def complete_entity(self, row, col=0, skip_bytes=True):
+        return self.rset.complete_entity(row, col, skip_bytes)
+
+    @deprecated('[3.5] use self.rset.get_entity(row,col) instead')
+    def entity(self, row, col=0):
+        return self.rset.get_entity(row, col)
+
+    @deprecated('[3.5] use req.user_rql_callback')
+    def user_rql_callback(self, args, msg=None):
+        return self.req.user_rql_callback(args, msg)
+
+    @deprecated('[3.5] use req.user_callback')
+    def user_callback(self, cb, args, msg=None, nonify=False):
+        return self.req.user_callback(cb, args, msg, nonify)
+
+    @deprecated('[3.5] use req.format_date')
     def format_date(self, date, date_format=None, time=False):
-        """return a string for a date time according to instance's
-        configuration
-        """
-        if date:
-            if date_format is None:
-                if time:
-                    date_format = self.req.property_value('ui.datetime-format')
-                else:
-                    date_format = self.req.property_value('ui.date-format')
-            return ustrftime(date, date_format)
-        return u''
+        return self.req.format_date(date, date_format, time)
 
+    @deprecated('[3.5] use req.format_timoe')
     def format_time(self, time):
-        """return a string for a time according to instance's
-        configuration
-        """
-        if time:
-            return ustrftime(time, self.req.property_value('ui.time-format'))
-        return u''
+        return self.req.format_time(time)
 
+    @deprecated('[3.5] use req.format_float')
     def format_float(self, num):
-        """return a string for floating point number according to instance's
-        configuration
-        """
-        if num:
-            return self.req.property_value('ui.float-format') % num
-        return u''
+        return self.req.format_float(num)
 
+    @deprecated('[3.5] use req.parse_datetime')
     def parse_datetime(self, value, etype='Datetime'):
-        """get a datetime or time from a string (according to etype)
-        Datetime formatted as Date are accepted
-        """
-        assert etype in ('Datetime', 'Date', 'Time'), etype
-        # XXX raise proper validation error
-        if etype == 'Datetime':
-            format = self.req.property_value('ui.datetime-format')
-            try:
-                return todatetime(strptime(value, format))
-            except ValueError:
-                pass
-        elif etype == 'Time':
-            format = self.req.property_value('ui.time-format')
-            try:
-                # (adim) I can't find a way to parse a Time with a custom format
-                date = strptime(value, format) # this returns a DateTime
-                return time(date.hour, date.minute, date.second)
-            except ValueError:
-                raise ValueError('can\'t parse %r (expected %s)' % (value, format))
-        try:
-            format = self.req.property_value('ui.date-format')
-            dt = strptime(value, format)
-            if etype == 'Datetime':
-                return todatetime(dt)
-            return todate(dt)
-        except ValueError:
-            raise ValueError('can\'t parse %r (expected %s)' % (value, format))
-
-    # security related methods ################################################
-
-    def ensure_ro_rql(self, rql):
-        """raise an exception if the given rql is not a select query"""
-        first = rql.split(' ', 1)[0].lower()
-        if first in ('insert', 'set', 'delete'):
-            raise Unauthorized(self.req._('only select queries are authorized'))
+        return self.req.parse_datetime(value, etype)
 
 set_log_methods(AppObject, getLogger('cubicweb.appobject'))
--- a/dbapi.py	Thu Aug 13 09:23:22 2009 +0200
+++ b/dbapi.py	Thu Aug 13 09:30:03 2009 +0200
@@ -19,8 +19,9 @@
 from logilab.common.decorators import monkeypatch
 from logilab.common.deprecation import deprecated
 
-from cubicweb import ETYPE_NAME_MAP, ConnectionError, RequestSessionMixIn
-from cubicweb import cwvreg, cwconfig
+from cubicweb import ETYPE_NAME_MAP, ConnectionError, cwvreg, cwconfig
+from cubicweb.req import RequestSessionBase
+
 
 _MARKER = object()
 
@@ -174,7 +175,7 @@
     return repo, cnx
 
 
-class DBAPIRequest(RequestSessionMixIn):
+class DBAPIRequest(RequestSessionBase):
 
     def __init__(self, vreg, cnx=None):
         super(DBAPIRequest, self).__init__(vreg)
--- a/devtools/fake.py	Thu Aug 13 09:23:22 2009 +0200
+++ b/devtools/fake.py	Thu Aug 13 09:30:03 2009 +0200
@@ -11,7 +11,7 @@
 
 from indexer import get_indexer
 
-from cubicweb import RequestSessionMixIn
+from cubicweb.req import RequestSessionBase
 from cubicweb.web.request import CubicWebRequestBase
 from cubicweb.devtools import BASE_URL, BaseApptestConfiguration
 
@@ -173,7 +173,7 @@
         return True
 
 
-class FakeSession(RequestSessionMixIn):
+class FakeSession(RequestSessionBase):
     def __init__(self, repo=None, user=None):
         self.repo = repo
         self.vreg = getattr(self.repo, 'vreg', FakeVReg())
--- a/goa/db.py	Thu Aug 13 09:23:22 2009 +0200
+++ b/goa/db.py	Thu Aug 13 09:30:03 2009 +0200
@@ -35,7 +35,8 @@
 
 from logilab.common.decorators import cached, iclassmethod
 
-from cubicweb import RequestSessionMixIn, Binary, entities
+from cubicweb import Binary, entities
+from cubicweb.req import RequestSessionBase
 from cubicweb.rset import ResultSet
 from cubicweb.entity import metaentity
 from cubicweb.server.utils import crypt_password
@@ -92,7 +93,7 @@
 def needrequest(wrapped):
     def wrapper(cls, *args, **kwargs):
         req = kwargs.pop('req', None)
-        if req is None and args and isinstance(args[0], RequestSessionMixIn):
+        if req is None and args and isinstance(args[0], RequestSessionBase):
             args = list(args)
             req = args.pop(0)
         if req is None:
@@ -155,7 +156,7 @@
         #
         # Entity prototype:
         #   __init__(self, req, rset, row=None, col=0)
-        if args and isinstance(args[0], RequestSessionMixIn) or 'req' in kwargs:
+        if args and isinstance(args[0], RequestSessionBase) or 'req' in kwargs:
             super(Model, self).__init__(*args, **kwargs)
             self._gaeinitargs = None
         else:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/req.py	Thu Aug 13 09:30:03 2009 +0200
@@ -0,0 +1,251 @@
+"""Base class for request/session
+
+:organization: Logilab
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: Library General Public License version 2 - http://www.gnu.org/licenses
+"""
+__docformat__ = "restructuredtext en"
+
+from datetime import time
+
+from logilab.common.decorators import cached
+
+from cubicweb import Unauthorized, typed_eid
+from cubicweb.rset import ResultSet
+from cubicweb.utils import ustrftime, strptime, todate, todatetime
+
+class RequestSessionBase(object):
+    """base class containing stuff shared by server session and web request
+    """
+    def __init__(self, vreg):
+        self.vreg = vreg
+        try:
+            encoding = vreg.property_value('ui.encoding')
+        except: # no vreg or property not registered
+            encoding = 'utf-8'
+        self.encoding = encoding
+        # cache result of execution for (rql expr / eids),
+        # should be emptied on commit/rollback of the server session / web
+        # connection
+        self.local_perm_cache = {}
+        self._ = unicode
+
+    def property_value(self, key):
+        """return value of the property with the given key, giving priority to
+        user specific value if any, else using site value
+        """
+        if self.user:
+            return self.user.property_value(key)
+        return self.vreg.property_value(key)
+
+    def etype_rset(self, etype, size=1):
+        """return a fake result set for a particular entity type"""
+        rset = ResultSet([('A',)]*size, '%s X' % etype,
+                         description=[(etype,)]*size)
+        def get_entity(row, col=0, etype=etype, req=self, rset=rset):
+            return req.vreg.etype_class(etype)(req, rset, row, col)
+        rset.get_entity = get_entity
+        return self.decorate_rset(rset)
+
+    def eid_rset(self, eid, etype=None):
+        """return a result set for the given eid without doing actual query
+        (we have the eid, we can suppose it exists and user has access to the
+        entity)
+        """
+        eid = typed_eid(eid)
+        if etype is None:
+            etype = self.describe(eid)[0]
+        rset = ResultSet([(eid,)], 'Any X WHERE X eid %(x)s', {'x': eid},
+                         [(etype,)])
+        return self.decorate_rset(rset)
+
+    def empty_rset(self):
+        """return a result set for the given eid without doing actual query
+        (we have the eid, we can suppose it exists and user has access to the
+        entity)
+        """
+        return self.decorate_rset(ResultSet([], 'Any X WHERE X eid -1'))
+
+    def entity_from_eid(self, eid, etype=None):
+        """return an entity instance for the given eid. No query is done"""
+        try:
+            return self.entity_cache(eid)
+        except KeyError:
+            rset = self.eid_rset(eid, etype)
+            entity = rset.get_entity(0, 0)
+            self.set_entity_cache(entity)
+            return entity
+
+    def entity_cache(self, eid):
+        raise KeyError
+
+    def set_entity_cache(self, entity):
+        pass
+
+    def ensure_ro_rql(self, rql):
+        """raise an exception if the given rql is not a select query"""
+        first = rql.split(' ', 1)[0].lower()
+        if first in ('insert', 'set', 'delete'):
+            raise Unauthorized(self._('only select queries are authorized'))
+
+    # url generation methods ##################################################
+
+    def build_url(self, *args, **kwargs):
+        """return an absolute URL using params dictionary key/values as URL
+        parameters. Values are automatically URL quoted, and the
+        publishing method to use may be specified or will be guessed.
+        """
+        # use *args since we don't want first argument to be "anonymous" to
+        # avoid potential clash with kwargs
+        assert len(args) == 1, 'only 0 or 1 non-named-argument expected'
+        method = args[0]
+        base_url = kwargs.pop('base_url', None)
+        if base_url is None:
+            base_url = self.base_url()
+        if '_restpath' in kwargs:
+            assert method == 'view', method
+            path = kwargs.pop('_restpath')
+        else:
+            path = method
+        if not kwargs:
+            return u'%s%s' % (base_url, path)
+        return u'%s%s?%s' % (base_url, path, self.build_url_params(**kwargs))
+
+
+    def build_url_params(self, **kwargs):
+        """return encoded params to incorporate them in an URL"""
+        args = []
+        for param, values in kwargs.items():
+            if not isinstance(values, (list, tuple)):
+                values = (values,)
+            for value in values:
+                args.append(u'%s=%s' % (param, self.url_quote(value)))
+        return '&'.join(args)
+
+    def url_quote(self, value, safe=''):
+        """urllib.quote is not unicode safe, use this method to do the
+        necessary encoding / decoding. Also it's designed to quote each
+        part of a url path and so the '/' character will be encoded as well.
+        """
+        if isinstance(value, unicode):
+            quoted = urlquote(value.encode(self.encoding), safe=safe)
+            return unicode(quoted, self.encoding)
+        return urlquote(str(value), safe=safe)
+
+    def url_unquote(self, quoted):
+        """returns a unicode unquoted string
+
+        decoding is based on `self.encoding` which is the encoding
+        used in `url_quote`
+        """
+        if isinstance(quoted, unicode):
+            quoted = quoted.encode(self.encoding)
+        try:
+            return unicode(urlunquote(quoted), self.encoding)
+        except UnicodeDecodeError: # might occurs on manually typed URLs
+            return unicode(urlunquote(quoted), 'iso-8859-1')
+
+    # bound user related methods ###############################################
+
+    @cached
+    def user_data(self):
+        """returns a dictionnary with this user's information"""
+        userinfo = {}
+        if self.is_internal_session:
+            userinfo['login'] = "cubicweb"
+            userinfo['name'] = "cubicweb"
+            userinfo['email'] = ""
+            return userinfo
+        user = self.actual_session().user
+        rql = "Any F,S,A where U eid %(x)s, U firstname F, U surname S, U primary_email E, E address A"
+        try:
+            firstname, lastname, email = self.execute(rql, {'x': user.eid}, 'x')[0]
+            if firstname is None and lastname is None:
+                userinfo['name'] = ''
+            else:
+                userinfo['name'] = ("%s %s" % (firstname, lastname))
+            userinfo['email'] = email
+        except IndexError:
+            userinfo['name'] = None
+            userinfo['email'] = None
+        userinfo['login'] = user.login
+        return userinfo
+
+    def is_internal_session(self):
+        """overrided on the server-side"""
+        return False
+
+    # formating methods #######################################################
+
+    def format_date(self, date, date_format=None, time=False):
+        """return a string for a date time according to instance's
+        configuration
+        """
+        if date:
+            if date_format is None:
+                if time:
+                    date_format = self.property_value('ui.datetime-format')
+                else:
+                    date_format = self.property_value('ui.date-format')
+            return ustrftime(date, date_format)
+        return u''
+
+    def format_time(self, time):
+        """return a string for a time according to instance's
+        configuration
+        """
+        if time:
+            return ustrftime(time, self.property_value('ui.time-format'))
+        return u''
+
+    def format_float(self, num):
+        """return a string for floating point number according to instance's
+        configuration
+        """
+        if num:
+            return self.property_value('ui.float-format') % num
+        return u''
+
+    def parse_datetime(self, value, etype='Datetime'):
+        """get a datetime or time from a string (according to etype)
+        Datetime formatted as Date are accepted
+        """
+        assert etype in ('Datetime', 'Date', 'Time'), etype
+        # XXX raise proper validation error
+        if etype == 'Datetime':
+            format = self.property_value('ui.datetime-format')
+            try:
+                return todatetime(strptime(value, format))
+            except ValueError:
+                pass
+        elif etype == 'Time':
+            format = self.property_value('ui.time-format')
+            try:
+                # (adim) I can't find a way to parse a Time with a custom format
+                date = strptime(value, format) # this returns a DateTime
+                return time(date.hour, date.minute, date.second)
+            except ValueError:
+                raise ValueError('can\'t parse %r (expected %s)' % (value, format))
+        try:
+            format = self.property_value('ui.date-format')
+            dt = strptime(value, format)
+            if etype == 'Datetime':
+                return todatetime(dt)
+            return todate(dt)
+        except ValueError:
+            raise ValueError('can\'t parse %r (expected %s)' % (value, format))
+
+    # abstract methods to override according to the web front-end #############
+
+    def base_url(self):
+        """return the root url of the instance"""
+        raise NotImplementedError
+
+    def decorate_rset(self, rset):
+        """add vreg/req (at least) attributes to the given result set """
+        raise NotImplementedError
+
+    def describe(self, eid):
+        """return a tuple (type, sourceuri, extid) for the entity with id <eid>"""
+        raise NotImplementedError
--- a/rset.py	Thu Aug 13 09:23:22 2009 +0200
+++ b/rset.py	Thu Aug 13 09:30:03 2009 +0200
@@ -9,7 +9,7 @@
 
 from logilab.common.decorators import cached, clear_cache, copy_cache
 
-from rql import nodes
+from rql import nodes, stmts
 
 from cubicweb import NotAnEntity
 
@@ -240,8 +240,6 @@
                 rset = mapping[key]
             rset.rows.append(self.rows[idx])
             rset.description.append(self.description[idx])
-
-
         for rset in result:
             rset.rowcount = len(rset.rows)
         if return_dict:
@@ -249,6 +247,51 @@
         else:
             return result
 
+    def limited_rql(self):
+        """return a printable rql for the result set associated to the object,
+        with limit/offset correctly set according to maximum page size and
+        currently displayed page when necessary
+        """
+        # try to get page boundaries from the navigation component
+        # XXX we should probably not have a ref to this component here (eg in
+        #     cubicweb.common)
+        nav = self.vreg['components'].select_or_none('navigation', self.req,
+                                                     rset=self)
+        if nav:
+            start, stop = nav.page_boundaries()
+            rql = self._limit_offset_rql(stop - start, start)
+        # result set may have be limited manually in which case navigation won't
+        # apply
+        elif self.limited:
+            rql = self._limit_offset_rql(*self.limited)
+        # navigation component doesn't apply and rset has not been limited, no
+        # need to limit query
+        else:
+            rql = self.printable_rql()
+        return rql
+
+    def _limit_offset_rql(self, limit, offset):
+        rqlst = self.syntax_tree()
+        if len(rqlst.children) == 1:
+            select = rqlst.children[0]
+            olimit, ooffset = select.limit, select.offset
+            select.limit, select.offset = limit, offset
+            rql = rqlst.as_string(kwargs=self.args)
+            # restore original limit/offset
+            select.limit, select.offset = olimit, ooffset
+        else:
+            newselect = stmts.Select()
+            newselect.limit = limit
+            newselect.offset = offset
+            aliases = [nodes.VariableRef(newselect.get_variable(vref.name, i))
+                       for i, vref in enumerate(rqlst.selection)]
+            newselect.set_with([nodes.SubQuery(aliases, rqlst)], check=False)
+            newunion = stmts.Union()
+            newunion.append(newselect)
+            rql = rqlst.as_string(kwargs=self.args)
+            rqlst.parent = None
+        return rql
+
     def limit(self, limit, offset=0, inplace=False):
         """limit the result set to the given number of rows optionaly starting
         from an index different than 0
@@ -318,6 +361,14 @@
             if self.rows[i][col] is not None:
                 yield self.get_entity(i, col)
 
+    def complete_entity(self, row, col=0, skip_bytes=True):
+        """short cut to get an completed entity instance for a particular
+        row (all instance's attributes have been fetched)
+        """
+        entity = self.get_entity(row, col)
+        entity.complete(skip_bytes=skip_bytes)
+        return entity
+
     @cached
     def get_entity(self, row, col=None):
         """special method for query retreiving a single entity, returns a
--- a/server/session.py	Thu Aug 13 09:23:22 2009 +0200
+++ b/server/session.py	Thu Aug 13 09:30:03 2009 +0200
@@ -15,7 +15,8 @@
 from rql.nodes import VariableRef, Function, ETYPE_PYOBJ_MAP, etype_from_pyobj
 from yams import BASE_TYPES
 
-from cubicweb import RequestSessionMixIn, Binary, UnknownEid
+from cubicweb import Binary, UnknownEid
+from cubicweb.req import RequestSessionBase
 from cubicweb.dbapi import ConnectionProperties
 from cubicweb.utils import make_uid
 from cubicweb.server.rqlrewrite import RQLRewriter
@@ -41,7 +42,7 @@
     return description
 
 
-class Session(RequestSessionMixIn):
+class Session(RequestSessionBase):
     """tie session id, user, connections pool and other session data all
     together
     """
--- a/web/controller.py	Thu Aug 13 09:23:22 2009 +0200
+++ b/web/controller.py	Thu Aug 13 09:30:03 2009 +0200
@@ -90,7 +90,7 @@
         # XXX assigning to self really necessary?
         self.rset = None
         if rql:
-            self.ensure_ro_rql(rql)
+            self.req.ensure_ro_rql(rql)
             if not isinstance(rql, unicode):
                 rql = unicode(rql, self.req.encoding)
             pp = self.vreg['components'].select_or_none('magicsearch', self.req)
--- a/web/request.py	Thu Aug 13 09:23:22 2009 +0200
+++ b/web/request.py	Thu Aug 13 09:30:03 2009 +0200
@@ -15,11 +15,12 @@
 from urlparse import urlsplit
 from itertools import count
 
+from simplejson import dumps
+
 from rql.utils import rqlvar_maker
 
 from logilab.common.decorators import cached
 from logilab.common.deprecation import deprecated
-
 from logilab.mtconverter import xml_escape
 
 from cubicweb.dbapi import DBAPIRequest
@@ -85,9 +86,16 @@
         self.next_tabindex = self.tabindexgen.next
         # page id, set by htmlheader template
         self.pageid = None
-        self.varmaker = rqlvar_maker()
         self.datadir_url = self._datadir_url()
 
+    @property
+    def varmaker(self)
+        varmaker = self.get_page_data('rql_varmaker')
+        if varmaker is None:
+            varmaker = rqlvar_maker()
+            self.set_page_data('rql_varmaker', varmaker)
+        return varmaker
+
     def set_connection(self, cnx, user=None):
         """method called by the session handler when the user is authenticated
         or an anonymous connection is open
@@ -257,6 +265,24 @@
             return breadcrumbs.pop()
         return self.base_url()
 
+    def user_rql_callback(self, args, msg=None):
+        """register a user callback to execute some rql query and return an url
+        to call it ready to be inserted in html
+        """
+        def rqlexec(req, rql, args=None, key=None):
+            req.execute(rql, args, key)
+        return self.user_callback(rqlexec, args, msg)
+
+    def user_callback(self, cb, args, msg=None, nonify=False):
+        """register the given user callback and return an url to call it ready to be
+        inserted in html
+        """
+        self.add_js('cubicweb.ajax.js')
+        cbname = self.register_onetime_callback(cb, *args)
+        msg = dumps(msg or '')
+        return "javascript:userCallbackThenReloadPage('%s', %s)" % (
+            cbname, msg)
+
     def register_onetime_callback(self, func, *args):
         cbname = 'cb_%s' % (
             sha.sha('%s%s%s%s' % (time.time(), func.__name__,
--- a/web/views/basecontrollers.py	Thu Aug 13 09:23:22 2009 +0200
+++ b/web/views/basecontrollers.py	Thu Aug 13 09:30:03 2009 +0200
@@ -290,7 +290,7 @@
     def _exec(self, rql, args=None, eidkey=None, rocheck=True):
         """json mode: execute RQL and return resultset as json"""
         if rocheck:
-            self.ensure_ro_rql(rql)
+            self.req.ensure_ro_rql(rql)
         try:
             return self.req.execute(rql, args, eidkey)
         except Exception, ex:
--- a/web/views/tableview.py	Thu Aug 13 09:23:22 2009 +0200
+++ b/web/views/tableview.py	Thu Aug 13 09:30:03 2009 +0200
@@ -293,7 +293,7 @@
              displaycols=None, displayactions=None, mainindex=None):
         """Dumps a table displaying a composite query"""
         actrql = self.req.form['actualrql']
-        self.ensure_ro_rql(actrql)
+        self.req.ensure_ro_rql(actrql)
         displaycols = self.displaycols(displaycols)
         if displayactions is None and 'displayactions' in self.req.form:
             displayactions = True