move view and appobject from cw.common to cw tls-sprint
authorsylvain.thenault@logilab.fr
Tue, 17 Feb 2009 21:57:05 +0100
branchtls-sprint
changeset 700 a2471775aef6
parent 699 cc149f4def1e
child 701 cd5e855de490
move view and appobject from cw.common to cw
appobject.py
common/appobject.py
common/view.py
view.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/appobject.py	Tue Feb 17 21:57:05 2009 +0100
@@ -0,0 +1,333 @@
+"""Base class for dynamically loaded objects manipulated in the web interface
+
+:organization: Logilab
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+__docformat__ = "restructuredtext en"
+
+from warnings import warn
+
+from mx.DateTime import now, oneSecond
+from simplejson import dumps
+
+from logilab.common.decorators import classproperty
+from logilab.common.deprecation import obsolete
+
+from rql.nodes import VariableRef, SubQuery
+from rql.stmts import Union, Select
+
+from cubicweb import Unauthorized
+from cubicweb.vregistry import VObject
+from cubicweb.selectors import yes
+from cubicweb.common.utils import UStringIO
+from cubicweb.common.uilib import html_escape, ustrftime
+from cubicweb.common.registerers import yes_registerer, priority_registerer
+
+
+
+class Cache(dict):    
+    def __init__(self):
+        super(Cache, self).__init__()
+        self.cache_creation_date = None
+        self.latest_cache_lookup = now()
+    
+CACHE_REGISTRY = {}
+
+class AppRsetObject(VObject):
+    """This is the base class for CubicWeb application objects
+    which are selected according to a request and result set.
+    
+    Classes are kept in the vregistry and instantiation is done at selection
+    time.
+    
+    At registration time, the following attributes are set on the class:
+    :vreg:
+      the application's registry
+    :schema:
+      the application's schema
+    :config:
+      the application's configuration
+
+    At instantiation time, the following attributes are set on the instance:
+    :req:
+      current request
+    :rset:
+      result set on which the object is applied
+    """
+
+    @classmethod
+    def registered(cls, vreg):
+        cls.vreg = vreg
+        cls.schema = vreg.schema
+        cls.config = vreg.config
+        cls.register_properties()
+        return cls
+
+    @classmethod
+    def selected(cls, *args, **kwargs):
+        """by default web app objects are usually instantiated on
+        selection according to a request, a result set, and optional
+        row and col
+        """
+        instance = cls(*args)
+        instance.row = kwargs.pop('row', None)
+        instance.col = kwargs.pop('col', None)
+        instance.selection_kwargs = kwargs
+        return instance
+
+    # Eproperties definition:
+    # key: id of the property (the actual EProperty key is build using
+    #      <registry name>.<obj id>.<property id>
+    # value: tuple (property type, vocabfunc, default value, property description)
+    #        possible types are those used by `logilab.common.configuration`
+    #
+    # notice that when it exists multiple objects with the same id (adaptation,
+    # overriding) only the first encountered definition is considered, so those
+    # objects can't try to have different default values for instance.
+    
+    property_defs = {}
+    
+    @classmethod
+    def register_properties(cls):
+        for propid, pdef in cls.property_defs.items():
+            pdef = pdef.copy() # may be shared
+            pdef['default'] = getattr(cls, propid, pdef['default'])
+            pdef['sitewide'] = getattr(cls, 'site_wide', pdef.get('sitewide'))
+            cls.vreg.register_property(cls.propkey(propid), **pdef)
+        
+    @classmethod
+    def propkey(cls, propid):
+        return '%s.%s.%s' % (cls.__registry__, cls.id, propid)
+
+    @classproperty
+    @obsolete('use __select__ and & or | operators')
+    def __selectors__(cls):
+        if isinstance(self.__select__, AndSelector):
+            return self.__select__.selectors
+        return self.__select__
+    
+    @classmethod
+    def __init__(self, req=None, rset=None):
+        super(AppRsetObject, self).__init__()
+        self.req = req
+        self.rset = rset
+
+    @property
+    def cursor(self): # XXX deprecate in favor of req.cursor?
+        msg = '.cursor is deprecated, use req.execute (or req.cursor if necessary)'
+        warn(msg, DeprecationWarning, stacklevel=2)
+        return self.req.cursor
+        
+    def get_cache(self, cachename):
+        """
+        NOTE: cachename should be dotted names as in :
+        - cubicweb.mycache
+        - cubes.blog.mycache 
+        - etc.
+        """
+        if cachename in CACHE_REGISTRY:
+            cache = CACHE_REGISTRY[cachename]
+        else:
+            cache = Cache()
+            CACHE_REGISTRY[cachename] = cache
+        _now = now()
+        if _now > cache.latest_cache_lookup + oneSecond:
+            ecache = self.req.execute('Any C,T WHERE C is ECache, C name %(name)s, C timestamp T', 
+                                      {'name':cachename}).get_entity(0,0)
+            cache.latest_cache_lookup = _now
+            if not ecache.valid(cache.cache_creation_date):
+                cache.empty()
+                cache.cache_creation_date = _now
+        return cache
+
+    def propval(self, propid):
+        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.select_component('navigation', self.req, 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
+    
+    # url generation methods ##################################################
+    
+    controller = 'view'
+    
+    def build_url(self, method=None, **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.
+        """
+        # XXX I (adim) think that if method is passed explicitly, we should
+        #     not try to process it and directly call req.build_url()
+        if method is None:
+            method = self.controller
+            if method == 'view' and self.req.from_controller() == 'view' and \
+                   not '_restpath' in kwargs:
+                method = self.req.relative_path(includeparams=False) or 'view'
+        return self.req.build_url(method, **kwargs)
+
+    # various resources accessors #############################################
+
+    def etype_rset(self, etype, size=1):
+        """return a fake result set for a particular entity type"""
+        msg = '.etype_rset is deprecated, use req.etype_rset'
+        warn(msg, DeprecationWarning, stacklevel=2)
+        return self.req.etype_rset(etype, size=1)
+
+    def eid_rset(self, eid, etype=None):
+        """return a result set for the given eid"""
+        msg = '.eid_rset is deprecated, use req.eid_rset'
+        warn(msg, DeprecationWarning, stacklevel=2)
+        return self.req.eid_rset(eid, etype)
+    
+    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.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
+        """
+        self.req.add_js('cubicweb.ajax.js')
+        if nonify:
+            # XXX < 2.48.3 bw compat
+            warn('nonify argument is deprecated', DeprecationWarning, stacklevel=2)
+            _cb = cb
+            def cb(*args):
+                _cb(*args)
+        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):
+        """render a precompiled page template with variables in the given
+        dictionary as context
+        """
+        from cubicweb.common.tal import CubicWebContext
+        context = CubicWebContext()
+        context.update({'self': self, 'rset': self.rset, '_' : self.req._,
+                        'req': self.req, 'user': self.req.user})
+        context.update(variables)
+        output = UStringIO()
+        template.expand(context, output)
+        return output.getvalue()
+
+    def format_date(self, date, date_format=None, time=False):
+        """return a string for a mx date time according to application'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''
+
+    def format_time(self, time):
+        """return a string for a mx date time according to application's
+        configuration
+        """
+        if time:
+            return ustrftime(time, self.req.property_value('ui.time-format'))
+        return u''
+
+    def format_float(self, num):
+        """return a string for floating point number according to application's
+        configuration
+        """
+        if num:
+            return self.req.property_value('ui.float-format') % num
+        return u''
+    
+    # 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'))
+
+        
+class AppObject(AppRsetObject):
+    """base class for application objects which are not selected
+    according to a result set, only by their identifier.
+    
+    Those objects may not have req, rset and cursor set.
+    """
+    
+    @classmethod
+    def selected(cls, *args, **kwargs):
+        """by default web app objects are usually instantiated on
+        selection
+        """
+        return cls(*args, **kwargs)
+
+    def __init__(self, req=None, rset=None, **kwargs):
+        self.req = req
+        self.rset = rset
+        self.__dict__.update(kwargs)
--- a/common/appobject.py	Tue Feb 17 21:50:41 2009 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,333 +0,0 @@
-"""Base class for dynamically loaded objects manipulated in the web interface
-
-:organization: Logilab
-:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-"""
-__docformat__ = "restructuredtext en"
-
-from warnings import warn
-
-from mx.DateTime import now, oneSecond
-from simplejson import dumps
-
-from logilab.common.decorators import classproperty
-from logilab.common.deprecation import obsolete
-
-from rql.nodes import VariableRef, SubQuery
-from rql.stmts import Union, Select
-
-from cubicweb import Unauthorized
-from cubicweb.vregistry import VObject
-from cubicweb.selectors import yes
-from cubicweb.common.utils import UStringIO
-from cubicweb.common.uilib import html_escape, ustrftime
-from cubicweb.common.registerers import yes_registerer, priority_registerer
-
-
-
-class Cache(dict):    
-    def __init__(self):
-        super(Cache, self).__init__()
-        self.cache_creation_date = None
-        self.latest_cache_lookup = now()
-    
-CACHE_REGISTRY = {}
-
-class AppRsetObject(VObject):
-    """This is the base class for CubicWeb application objects
-    which are selected according to a request and result set.
-    
-    Classes are kept in the vregistry and instantiation is done at selection
-    time.
-    
-    At registration time, the following attributes are set on the class:
-    :vreg:
-      the application's registry
-    :schema:
-      the application's schema
-    :config:
-      the application's configuration
-
-    At instantiation time, the following attributes are set on the instance:
-    :req:
-      current request
-    :rset:
-      result set on which the object is applied
-    """
-
-    @classmethod
-    def registered(cls, vreg):
-        cls.vreg = vreg
-        cls.schema = vreg.schema
-        cls.config = vreg.config
-        cls.register_properties()
-        return cls
-
-    @classmethod
-    def selected(cls, *args, **kwargs):
-        """by default web app objects are usually instantiated on
-        selection according to a request, a result set, and optional
-        row and col
-        """
-        instance = cls(*args)
-        instance.row = kwargs.pop('row', None)
-        instance.col = kwargs.pop('col', None)
-        instance.selection_kwargs = kwargs
-        return instance
-
-    # Eproperties definition:
-    # key: id of the property (the actual EProperty key is build using
-    #      <registry name>.<obj id>.<property id>
-    # value: tuple (property type, vocabfunc, default value, property description)
-    #        possible types are those used by `logilab.common.configuration`
-    #
-    # notice that when it exists multiple objects with the same id (adaptation,
-    # overriding) only the first encountered definition is considered, so those
-    # objects can't try to have different default values for instance.
-    
-    property_defs = {}
-    
-    @classmethod
-    def register_properties(cls):
-        for propid, pdef in cls.property_defs.items():
-            pdef = pdef.copy() # may be shared
-            pdef['default'] = getattr(cls, propid, pdef['default'])
-            pdef['sitewide'] = getattr(cls, 'site_wide', pdef.get('sitewide'))
-            cls.vreg.register_property(cls.propkey(propid), **pdef)
-        
-    @classmethod
-    def propkey(cls, propid):
-        return '%s.%s.%s' % (cls.__registry__, cls.id, propid)
-
-    @classproperty
-    @obsolete('use __select__ and & or | operators')
-    def __selectors__(cls):
-        if isinstance(self.__select__, AndSelector):
-            return self.__select__.selectors
-        return self.__select__
-    
-    @classmethod
-    def __init__(self, req=None, rset=None):
-        super(AppRsetObject, self).__init__()
-        self.req = req
-        self.rset = rset
-
-    @property
-    def cursor(self): # XXX deprecate in favor of req.cursor?
-        msg = '.cursor is deprecated, use req.execute (or req.cursor if necessary)'
-        warn(msg, DeprecationWarning, stacklevel=2)
-        return self.req.cursor
-        
-    def get_cache(self, cachename):
-        """
-        NOTE: cachename should be dotted names as in :
-        - cubicweb.mycache
-        - cubes.blog.mycache 
-        - etc.
-        """
-        if cachename in CACHE_REGISTRY:
-            cache = CACHE_REGISTRY[cachename]
-        else:
-            cache = Cache()
-            CACHE_REGISTRY[cachename] = cache
-        _now = now()
-        if _now > cache.latest_cache_lookup + oneSecond:
-            ecache = self.req.execute('Any C,T WHERE C is ECache, C name %(name)s, C timestamp T', 
-                                      {'name':cachename}).get_entity(0,0)
-            cache.latest_cache_lookup = _now
-            if not ecache.valid(cache.cache_creation_date):
-                cache.empty()
-                cache.cache_creation_date = _now
-        return cache
-
-    def propval(self, propid):
-        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.select_component('navigation', self.req, 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
-    
-    # url generation methods ##################################################
-    
-    controller = 'view'
-    
-    def build_url(self, method=None, **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.
-        """
-        # XXX I (adim) think that if method is passed explicitly, we should
-        #     not try to process it and directly call req.build_url()
-        if method is None:
-            method = self.controller
-            if method == 'view' and self.req.from_controller() == 'view' and \
-                   not '_restpath' in kwargs:
-                method = self.req.relative_path(includeparams=False) or 'view'
-        return self.req.build_url(method, **kwargs)
-
-    # various resources accessors #############################################
-
-    def etype_rset(self, etype, size=1):
-        """return a fake result set for a particular entity type"""
-        msg = '.etype_rset is deprecated, use req.etype_rset'
-        warn(msg, DeprecationWarning, stacklevel=2)
-        return self.req.etype_rset(etype, size=1)
-
-    def eid_rset(self, eid, etype=None):
-        """return a result set for the given eid"""
-        msg = '.eid_rset is deprecated, use req.eid_rset'
-        warn(msg, DeprecationWarning, stacklevel=2)
-        return self.req.eid_rset(eid, etype)
-    
-    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.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
-        """
-        self.req.add_js('cubicweb.ajax.js')
-        if nonify:
-            # XXX < 2.48.3 bw compat
-            warn('nonify argument is deprecated', DeprecationWarning, stacklevel=2)
-            _cb = cb
-            def cb(*args):
-                _cb(*args)
-        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):
-        """render a precompiled page template with variables in the given
-        dictionary as context
-        """
-        from cubicweb.common.tal import CubicWebContext
-        context = CubicWebContext()
-        context.update({'self': self, 'rset': self.rset, '_' : self.req._,
-                        'req': self.req, 'user': self.req.user})
-        context.update(variables)
-        output = UStringIO()
-        template.expand(context, output)
-        return output.getvalue()
-
-    def format_date(self, date, date_format=None, time=False):
-        """return a string for a mx date time according to application'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''
-
-    def format_time(self, time):
-        """return a string for a mx date time according to application's
-        configuration
-        """
-        if time:
-            return ustrftime(time, self.req.property_value('ui.time-format'))
-        return u''
-
-    def format_float(self, num):
-        """return a string for floating point number according to application's
-        configuration
-        """
-        if num:
-            return self.req.property_value('ui.float-format') % num
-        return u''
-    
-    # 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'))
-
-        
-class AppObject(AppRsetObject):
-    """base class for application objects which are not selected
-    according to a result set, only by their identifier.
-    
-    Those objects may not have req, rset and cursor set.
-    """
-    
-    @classmethod
-    def selected(cls, *args, **kwargs):
-        """by default web app objects are usually instantiated on
-        selection
-        """
-        return cls(*args, **kwargs)
-
-    def __init__(self, req=None, rset=None, **kwargs):
-        self.req = req
-        self.rset = rset
-        self.__dict__.update(kwargs)
--- a/common/view.py	Tue Feb 17 21:50:41 2009 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,514 +0,0 @@
-"""abstract views and templates classes for CubicWeb web client
-
-
-:organization: Logilab
-:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-"""
-__docformat__ = "restructuredtext en"
-
-from cStringIO import StringIO
-
-from logilab.mtconverter import html_escape
-
-from cubicweb import NotAnEntity, NoSelectableObject
-from cubicweb.selectors import (yes, match_user_groups, implements,
-                                nonempty_rset, none_rset)
-from cubicweb.selectors import require_group_compat, accepts_compat
-from cubicweb.common.registerers import accepts_registerer, priority_registerer, yes_registerer
-from cubicweb.common.appobject import AppRsetObject
-from cubicweb.common.utils import UStringIO, HTMLStream
-
-_ = unicode
-
-
-# robots control
-NOINDEX = u'<meta name="ROBOTS" content="NOINDEX" />'
-NOFOLLOW = u'<meta name="ROBOTS" content="NOFOLLOW" />'
-
-CW_XHTML_EXTENSIONS = '''[
-  <!ATTLIST html xmlns:cubicweb CDATA  #FIXED \'http://www.logilab.org/2008/cubicweb\'  >
-
-<!ENTITY % coreattrs
- "id          ID            #IMPLIED
-  class       CDATA         #IMPLIED
-  style       CDATA         #IMPLIED
-  title       CDATA         #IMPLIED
-
- cubicweb:sortvalue         CDATA   #IMPLIED
- cubicweb:target            CDATA   #IMPLIED
- cubicweb:limit             CDATA   #IMPLIED
- cubicweb:type              CDATA   #IMPLIED
- cubicweb:loadtype          CDATA   #IMPLIED
- cubicweb:wdgtype           CDATA   #IMPLIED
- cubicweb:initfunc          CDATA   #IMPLIED
- cubicweb:inputid           CDATA   #IMPLIED
- cubicweb:tindex            CDATA   #IMPLIED
- cubicweb:inputname         CDATA   #IMPLIED
- cubicweb:value             CDATA   #IMPLIED
- cubicweb:required          CDATA   #IMPLIED
- cubicweb:accesskey         CDATA   #IMPLIED
- cubicweb:maxlength         CDATA   #IMPLIED
- cubicweb:variables         CDATA   #IMPLIED
- cubicweb:displayactions    CDATA   #IMPLIED
- cubicweb:fallbackvid       CDATA   #IMPLIED
- cubicweb:vid               CDATA   #IMPLIED
- cubicweb:rql               CDATA   #IMPLIED
- cubicweb:actualrql         CDATA   #IMPLIED
- cubicweb:rooteid           CDATA   #IMPLIED
- cubicweb:dataurl           CDATA   #IMPLIED
- cubicweb:size              CDATA   #IMPLIED
- cubicweb:tlunit            CDATA   #IMPLIED
- cubicweb:loadurl           CDATA   #IMPLIED
- cubicweb:uselabel          CDATA   #IMPLIED
- cubicweb:facetargs         CDATA   #IMPLIED
- cubicweb:facetName         CDATA   #IMPLIED
-  "> ] '''
-
-TRANSITIONAL_DOCTYPE = u'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" %s>\n'
-
-STRICT_DOCTYPE = u'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd" %s>\n'
-
-# base view object ############################################################
-
-class View(AppRsetObject):
-    """abstract view class, used as base for every renderable object such
-    as views, templates, some components...web
-
-    A view is instantiated to render a [part of a] result set. View
-    subclasses may be parametred using the following class attributes:
-
-    * `templatable` indicates if the view may be embeded in a main
-      template or if it has to be rendered standalone (i.e. XML for
-      instance)
-    * if the view is not templatable, it should set the `content_type` class
-      attribute to the correct MIME type (text/xhtml by default)
-    * the `category` attribute may be used in the interface to regroup related
-      objects together
-
-    At instantiation time, the standard `req`, `rset`, and `cursor`
-    attributes are added and the `w` attribute will be set at rendering
-    time to a write function to use.
-    """
-    __registerer__ = priority_registerer
-    __registry__ = 'views'
-
-    templatable = True
-    need_navigation = True
-    # content_type = 'application/xhtml+xml' # text/xhtml'
-    binary = False
-    add_to_breadcrumbs = True
-    category = 'view'
-
-    def __init__(self, req=None, rset=None):
-        super(View, self).__init__(req, rset)
-        self.w = None
-
-    @property
-    def content_type(self):
-        if self.req.xhtml_browser():
-            return 'application/xhtml+xml'
-        return 'text/html'
-
-    def set_stream(self, w=None):
-        if self.w is not None:
-            return
-        if w is None:
-            if self.binary:
-                self._stream = stream = StringIO()
-            else:
-                self._stream = stream = UStringIO()
-            w = stream.write
-        else:
-            stream = None
-        self.w = w
-        return stream
-
-    # main view interface #####################################################
-
-    def dispatch(self, w=None, **context):
-        """called to render a view object for a result set.
-
-        This method is a dispatched to an actual method selected
-        according to optional row and col parameters, which are locating
-        a particular row or cell in the result set:
-
-        * if row [and col] are specified, `cell_call` is called
-        * if none of them is supplied, the view is considered to apply on
-          the whole result set (which may be None in this case), `call` is
-          called
-        """
-        row, col = context.get('row'), context.get('col')
-        if row is not None:
-            context.setdefault('col', 0)
-            view_func = self.cell_call
-        else:
-            view_func = self.call
-        stream = self.set_stream(w)
-        # stream = self.set_stream(context)
-        view_func(**context)
-        # return stream content if we have created it
-        if stream is not None:
-            return self._stream.getvalue()
-
-    # should default .call() method add a <div classs="section"> around each
-    # rset item
-    add_div_section = True
-
-    def call(self, **kwargs):
-        """the view is called for an entire result set, by default loop
-        other rows of the result set and call the same view on the
-        particular row
-
-        Views applicable on None result sets have to override this method
-        """
-        rset = self.rset
-        if rset is None:
-            raise NotImplementedError, self
-        wrap = self.templatable and len(rset) > 1 and self.add_div_section
-        for i in xrange(len(rset)):
-            if wrap:
-                self.w(u'<div class="section">')
-            self.wview(self.id, rset, row=i, **kwargs)
-            if wrap:
-                self.w(u"</div>")
-
-    def cell_call(self, row, col, **kwargs):
-        """the view is called for a particular result set cell"""
-        raise NotImplementedError, self
-
-    def linkable(self):
-        """return True if the view may be linked in a menu
-
-        by default views without title are not meant to be displayed
-        """
-        if not getattr(self, 'title', None):
-            return False
-        return True
-
-    def is_primary(self):
-        return self.id == 'primary'
-
-    def url(self):
-        """return the url associated with this view. Should not be
-        necessary for non linkable views, but a default implementation
-        is provided anyway.
-        """
-        try:
-            return self.build_url(vid=self.id, rql=self.req.form['rql'])
-        except KeyError:
-            return self.build_url(vid=self.id)
-
-    def set_request_content_type(self):
-        """set the content type returned by this view"""
-        self.req.set_content_type(self.content_type)
-
-    # view utilities ##########################################################
-
-    def view(self, __vid, rset, __fallback_vid=None, **kwargs):
-        """shortcut to self.vreg.render method avoiding to pass self.req"""
-        try:
-            view = self.vreg.select_view(__vid, self.req, rset, **kwargs)
-        except NoSelectableObject:
-            if __fallback_vid is None:
-                raise
-            view = self.vreg.select_view(__fallback_vid, self.req, rset, **kwargs)
-        return view.dispatch(**kwargs)
-
-    def wview(self, __vid, rset, __fallback_vid=None, **kwargs):
-        """shortcut to self.view method automatically passing self.w as argument
-        """
-        self.view(__vid, rset, __fallback_vid, w=self.w, **kwargs)
-
-    def whead(self, data):
-        self.req.html_headers.write(data)
-
-    def wdata(self, data):
-        """simple helper that escapes `data` and writes into `self.w`"""
-        self.w(html_escape(data))
-
-    def action(self, actionid, row=0):
-        """shortcut to get action object with id `actionid`"""
-        return self.vreg.select_action(actionid, self.req, self.rset,
-                                       row=row)
-
-    def action_url(self, actionid, label=None, row=0):
-        """simple method to be able to display `actionid` as a link anywhere
-        """
-        action = self.vreg.select_action(actionid, self.req, self.rset,
-                                         row=row)
-        if action:
-            label = label or self.req._(action.title)
-            return u'<a href="%s">%s</a>' % (html_escape(action.url()), label)
-        return u''
-
-    def html_headers(self):
-        """return a list of html headers (eg something to be inserted between
-        <head> and </head> of the returned page
-
-        by default return a meta tag to disable robot indexation of the page
-        """
-        return [NOINDEX]
-
-    def page_title(self):
-        """returns a title according to the result set - used for the
-        title in the HTML header
-        """
-        vtitle = self.req.form.get('vtitle')
-        if vtitle:
-            return self.req._(vtitle)
-        # class defined title will only be used if the resulting title doesn't
-        # seem clear enough
-        vtitle = getattr(self, 'title', None) or u''
-        if vtitle:
-            vtitle = self.req._(vtitle)
-        rset = self.rset
-        if rset and rset.rowcount:
-            if rset.rowcount == 1:
-                try:
-                    entity = self.complete_entity(0)
-                    # use long_title to get context information if any
-                    clabel = entity.dc_long_title()
-                except NotAnEntity:
-                    clabel = display_name(self.req, rset.description[0][0])
-                    clabel = u'%s (%s)' % (clabel, vtitle)
-            else :
-                etypes = rset.column_types(0)
-                if len(etypes) == 1:
-                    etype = iter(etypes).next()
-                    clabel = display_name(self.req, etype, 'plural')
-                else :
-                    clabel = u'#[*] (%s)' % vtitle
-        else:
-            clabel = vtitle
-        return u'%s (%s)' % (clabel, self.req.property_value('ui.site-title'))
-
-    def output_url_builder( self, name, url, args ):
-        self.w(u'<script language="JavaScript"><!--\n' \
-               u'function %s( %s ) {\n' % (name, ','.join(args) ) )
-        url_parts = url.split("%s")
-        self.w(u' url="%s"' % url_parts[0] )
-        for arg, part in zip(args, url_parts[1:]):
-            self.w(u'+str(%s)' % arg )
-            if part:
-                self.w(u'+"%s"' % part)
-        self.w('\n document.window.href=url;\n')
-        self.w('}\n-->\n</script>\n')
-
-    def create_url(self, etype, **kwargs):
-        """ return the url of the entity creation form for a given entity type"""
-        return self.req.build_url('add/%s'%etype, **kwargs)
-    
-    def field(self, label, value, row=True, show_label=True, w=None, tr=True):
-        """ read-only field """
-        if w is None:
-            w = self.w
-        if row:
-            w(u'<div class="row">')
-        if show_label:
-            if tr:
-                label = display_name(self.req, label)
-            w(u'<span class="label">%s</span>' % label)
-        w(u'<div class="field">%s</div>' % value)
-        if row:
-            w(u'</div>')
-
-
-# concrete views base classes #################################################
-
-class EntityView(View):
-    """base class for views applying on an entity (i.e. uniform result set)
-    """
-    # XXX deprecate
-    __registerer__ = accepts_registerer
-    __selectors__ = (implements('Any'),)
-    registered = accepts_compat(View.registered.im_func)
-
-    category = 'entityview'
-
-
-class StartupView(View):
-    """base class for views which doesn't need a particular result set
-    to be displayed (so they can always be displayed !)
-    """
-    __registerer__ = priority_registerer
-    __selectors__ = (none_rset,)
-    registered = require_group_compat(View.registered.im_func)
-    
-    category = 'startupview'
-    
-    def url(self):
-        """return the url associated with this view. We can omit rql here"""
-        return self.build_url('view', vid=self.id)
-
-    def html_headers(self):
-        """return a list of html headers (eg something to be inserted between
-        <head> and </head> of the returned page
-
-        by default startup views are indexed
-        """
-        return []
-
-
-class EntityStartupView(EntityView):
-    """base class for entity views which may also be applied to None
-    result set (usually a default rql is provided by the view class)
-    """
-    __selectors__ = ((none_rset | implements('Any')),)
-
-    default_rql = None
-
-    def __init__(self, req, rset):
-        super(EntityStartupView, self).__init__(req, rset)
-        if rset is None:
-            # this instance is not in the "entityview" category
-            self.category = 'startupview'
-
-    def startup_rql(self):
-        """return some rql to be executedif the result set is None"""
-        return self.default_rql
-
-    def call(self, **kwargs):
-        """override call to execute rql returned by the .startup_rql
-        method if necessary
-        """
-        if self.rset is None:
-            self.rset = self.req.execute(self.startup_rql())
-        rset = self.rset
-        for i in xrange(len(rset)):
-            self.wview(self.id, rset, row=i, **kwargs)
-
-    def url(self):
-        """return the url associated with this view. We can omit rql if we
-        are on a result set on which we do not apply.
-        """
-        if not self.__select__(self.req, self.rset):
-            return self.build_url(vid=self.id)
-        return super(EntityStartupView, self).url()
-
-
-class AnyRsetView(View):
-    """base class for views applying on any non empty result sets"""
-    __selectors__ = (nonempty_rset,)
-
-    category = 'anyrsetview'
-
-    def columns_labels(self, tr=True):
-        if tr:
-            translate = display_name
-        else:
-            translate = lambda req, val: val
-        rqlstdescr = self.rset.syntax_tree().get_description()[0] # XXX missing Union support
-        labels = []
-        for colindex, attr in enumerate(rqlstdescr):
-            # compute column header
-            if colindex == 0 or attr == 'Any': # find a better label
-                label = ','.join(translate(self.req, et)
-                                 for et in self.rset.column_types(colindex))
-            else:
-                label = translate(self.req, attr)
-            labels.append(label)
-        return labels
-
-    
-# concrete template base classes ##############################################
-
-class Template(View):
-    """a template is almost like a view, except that by default a template
-    is only used globally (i.e. no result set adaptation)
-    """
-    __registry__ = 'templates'
-    __selectors__ = (yes,)
-
-    registered = require_group_compat(View.registered.im_func)
-
-    def template(self, oid, **kwargs):
-        """shortcut to self.registry.render method on the templates registry"""
-        w = kwargs.pop('w', self.w)
-        self.vreg.render('templates', oid, self.req, w=w, **kwargs)
-
-
-class MainTemplate(Template):
-    """main template are primary access point to render a full HTML page.
-    There is usually at least a regular main template and a simple fallback
-    one to display error if the first one failed
-    """
-    base_doctype = STRICT_DOCTYPE
-
-    @property
-    def doctype(self):
-        if self.req.xhtml_browser():
-            return self.base_doctype % CW_XHTML_EXTENSIONS
-        return self.base_doctype % ''
-
-    def set_stream(self, w=None, templatable=True):
-        if templatable and self.w is not None:
-            return
-
-        if w is None:
-            if self.binary:
-                self._stream = stream = StringIO()
-            elif not templatable:
-                # not templatable means we're using a non-html view, we don't
-                # want the HTMLStream stuff to interfere during data generation
-                self._stream = stream = UStringIO()
-            else:
-                self._stream = stream = HTMLStream(self.req)
-            w = stream.write
-        else:
-            stream = None
-        self.w = w
-        return stream
-
-    def write_doctype(self, xmldecl=True):
-        assert isinstance(self._stream, HTMLStream)
-        self._stream.doctype = self.doctype
-        if not xmldecl:
-            self._stream.xmldecl = u''
-
-# concrete component base classes #############################################
-
-class ReloadableMixIn(object):
-    """simple mixin for reloadable parts of UI"""
-    
-    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.req.add_js('cubicweb.ajax.js')
-        if nonify:
-            _cb = cb
-            def cb(*args):
-                _cb(*args)
-        cbname = self.req.register_onetime_callback(cb, *args)
-        return self.build_js(cbname, html_escape(msg or ''))
-        
-    def build_update_js_call(self, cbname, msg):
-        rql = html_escape(self.rset.printable_rql())
-        return "javascript:userCallbackThenUpdateUI('%s', '%s', '%s', '%s', '%s', '%s')" % (
-            cbname, self.id, rql, msg, self.__registry__, self.div_id())
-    
-    def build_reload_js_call(self, cbname, msg):
-        return "javascript:userCallbackThenReloadPage('%s', '%s')" % (cbname, msg)
-
-    build_js = build_update_js_call # expect updatable component by default
-    
-    def div_id(self):
-        return ''
-
-
-class Component(ReloadableMixIn, View):
-    """base class for components"""
-    __registry__ = 'components'
-    __registerer__ = yes_registerer
-    __selectors__ = (yes,)
-    property_defs = {
-        _('visible'):  dict(type='Boolean', default=True,
-                            help=_('display the box or not')),
-        }    
-
-    def div_class(self):
-        return '%s %s' % (self.propval('htmlclass'), self.id)
-
-    def div_id(self):
-        return '%sComponent' % self.id
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/view.py	Tue Feb 17 21:57:05 2009 +0100
@@ -0,0 +1,514 @@
+"""abstract views and templates classes for CubicWeb web client
+
+
+:organization: Logilab
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+__docformat__ = "restructuredtext en"
+
+from cStringIO import StringIO
+
+from logilab.mtconverter import html_escape
+
+from cubicweb import NotAnEntity, NoSelectableObject
+from cubicweb.selectors import (yes, match_user_groups, implements,
+                                nonempty_rset, none_rset)
+from cubicweb.selectors import require_group_compat, accepts_compat
+from cubicweb.common.registerers import accepts_registerer, priority_registerer, yes_registerer
+from cubicweb.common.appobject import AppRsetObject
+from cubicweb.common.utils import UStringIO, HTMLStream
+
+_ = unicode
+
+
+# robots control
+NOINDEX = u'<meta name="ROBOTS" content="NOINDEX" />'
+NOFOLLOW = u'<meta name="ROBOTS" content="NOFOLLOW" />'
+
+CW_XHTML_EXTENSIONS = '''[
+  <!ATTLIST html xmlns:cubicweb CDATA  #FIXED \'http://www.logilab.org/2008/cubicweb\'  >
+
+<!ENTITY % coreattrs
+ "id          ID            #IMPLIED
+  class       CDATA         #IMPLIED
+  style       CDATA         #IMPLIED
+  title       CDATA         #IMPLIED
+
+ cubicweb:sortvalue         CDATA   #IMPLIED
+ cubicweb:target            CDATA   #IMPLIED
+ cubicweb:limit             CDATA   #IMPLIED
+ cubicweb:type              CDATA   #IMPLIED
+ cubicweb:loadtype          CDATA   #IMPLIED
+ cubicweb:wdgtype           CDATA   #IMPLIED
+ cubicweb:initfunc          CDATA   #IMPLIED
+ cubicweb:inputid           CDATA   #IMPLIED
+ cubicweb:tindex            CDATA   #IMPLIED
+ cubicweb:inputname         CDATA   #IMPLIED
+ cubicweb:value             CDATA   #IMPLIED
+ cubicweb:required          CDATA   #IMPLIED
+ cubicweb:accesskey         CDATA   #IMPLIED
+ cubicweb:maxlength         CDATA   #IMPLIED
+ cubicweb:variables         CDATA   #IMPLIED
+ cubicweb:displayactions    CDATA   #IMPLIED
+ cubicweb:fallbackvid       CDATA   #IMPLIED
+ cubicweb:vid               CDATA   #IMPLIED
+ cubicweb:rql               CDATA   #IMPLIED
+ cubicweb:actualrql         CDATA   #IMPLIED
+ cubicweb:rooteid           CDATA   #IMPLIED
+ cubicweb:dataurl           CDATA   #IMPLIED
+ cubicweb:size              CDATA   #IMPLIED
+ cubicweb:tlunit            CDATA   #IMPLIED
+ cubicweb:loadurl           CDATA   #IMPLIED
+ cubicweb:uselabel          CDATA   #IMPLIED
+ cubicweb:facetargs         CDATA   #IMPLIED
+ cubicweb:facetName         CDATA   #IMPLIED
+  "> ] '''
+
+TRANSITIONAL_DOCTYPE = u'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" %s>\n'
+
+STRICT_DOCTYPE = u'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd" %s>\n'
+
+# base view object ############################################################
+
+class View(AppRsetObject):
+    """abstract view class, used as base for every renderable object such
+    as views, templates, some components...web
+
+    A view is instantiated to render a [part of a] result set. View
+    subclasses may be parametred using the following class attributes:
+
+    * `templatable` indicates if the view may be embeded in a main
+      template or if it has to be rendered standalone (i.e. XML for
+      instance)
+    * if the view is not templatable, it should set the `content_type` class
+      attribute to the correct MIME type (text/xhtml by default)
+    * the `category` attribute may be used in the interface to regroup related
+      objects together
+
+    At instantiation time, the standard `req`, `rset`, and `cursor`
+    attributes are added and the `w` attribute will be set at rendering
+    time to a write function to use.
+    """
+    __registerer__ = priority_registerer
+    __registry__ = 'views'
+
+    templatable = True
+    need_navigation = True
+    # content_type = 'application/xhtml+xml' # text/xhtml'
+    binary = False
+    add_to_breadcrumbs = True
+    category = 'view'
+
+    def __init__(self, req=None, rset=None):
+        super(View, self).__init__(req, rset)
+        self.w = None
+
+    @property
+    def content_type(self):
+        if self.req.xhtml_browser():
+            return 'application/xhtml+xml'
+        return 'text/html'
+
+    def set_stream(self, w=None):
+        if self.w is not None:
+            return
+        if w is None:
+            if self.binary:
+                self._stream = stream = StringIO()
+            else:
+                self._stream = stream = UStringIO()
+            w = stream.write
+        else:
+            stream = None
+        self.w = w
+        return stream
+
+    # main view interface #####################################################
+
+    def dispatch(self, w=None, **context):
+        """called to render a view object for a result set.
+
+        This method is a dispatched to an actual method selected
+        according to optional row and col parameters, which are locating
+        a particular row or cell in the result set:
+
+        * if row [and col] are specified, `cell_call` is called
+        * if none of them is supplied, the view is considered to apply on
+          the whole result set (which may be None in this case), `call` is
+          called
+        """
+        row, col = context.get('row'), context.get('col')
+        if row is not None:
+            context.setdefault('col', 0)
+            view_func = self.cell_call
+        else:
+            view_func = self.call
+        stream = self.set_stream(w)
+        # stream = self.set_stream(context)
+        view_func(**context)
+        # return stream content if we have created it
+        if stream is not None:
+            return self._stream.getvalue()
+
+    # should default .call() method add a <div classs="section"> around each
+    # rset item
+    add_div_section = True
+
+    def call(self, **kwargs):
+        """the view is called for an entire result set, by default loop
+        other rows of the result set and call the same view on the
+        particular row
+
+        Views applicable on None result sets have to override this method
+        """
+        rset = self.rset
+        if rset is None:
+            raise NotImplementedError, self
+        wrap = self.templatable and len(rset) > 1 and self.add_div_section
+        for i in xrange(len(rset)):
+            if wrap:
+                self.w(u'<div class="section">')
+            self.wview(self.id, rset, row=i, **kwargs)
+            if wrap:
+                self.w(u"</div>")
+
+    def cell_call(self, row, col, **kwargs):
+        """the view is called for a particular result set cell"""
+        raise NotImplementedError, self
+
+    def linkable(self):
+        """return True if the view may be linked in a menu
+
+        by default views without title are not meant to be displayed
+        """
+        if not getattr(self, 'title', None):
+            return False
+        return True
+
+    def is_primary(self):
+        return self.id == 'primary'
+
+    def url(self):
+        """return the url associated with this view. Should not be
+        necessary for non linkable views, but a default implementation
+        is provided anyway.
+        """
+        try:
+            return self.build_url(vid=self.id, rql=self.req.form['rql'])
+        except KeyError:
+            return self.build_url(vid=self.id)
+
+    def set_request_content_type(self):
+        """set the content type returned by this view"""
+        self.req.set_content_type(self.content_type)
+
+    # view utilities ##########################################################
+
+    def view(self, __vid, rset, __fallback_vid=None, **kwargs):
+        """shortcut to self.vreg.render method avoiding to pass self.req"""
+        try:
+            view = self.vreg.select_view(__vid, self.req, rset, **kwargs)
+        except NoSelectableObject:
+            if __fallback_vid is None:
+                raise
+            view = self.vreg.select_view(__fallback_vid, self.req, rset, **kwargs)
+        return view.dispatch(**kwargs)
+
+    def wview(self, __vid, rset, __fallback_vid=None, **kwargs):
+        """shortcut to self.view method automatically passing self.w as argument
+        """
+        self.view(__vid, rset, __fallback_vid, w=self.w, **kwargs)
+
+    def whead(self, data):
+        self.req.html_headers.write(data)
+
+    def wdata(self, data):
+        """simple helper that escapes `data` and writes into `self.w`"""
+        self.w(html_escape(data))
+
+    def action(self, actionid, row=0):
+        """shortcut to get action object with id `actionid`"""
+        return self.vreg.select_action(actionid, self.req, self.rset,
+                                       row=row)
+
+    def action_url(self, actionid, label=None, row=0):
+        """simple method to be able to display `actionid` as a link anywhere
+        """
+        action = self.vreg.select_action(actionid, self.req, self.rset,
+                                         row=row)
+        if action:
+            label = label or self.req._(action.title)
+            return u'<a href="%s">%s</a>' % (html_escape(action.url()), label)
+        return u''
+
+    def html_headers(self):
+        """return a list of html headers (eg something to be inserted between
+        <head> and </head> of the returned page
+
+        by default return a meta tag to disable robot indexation of the page
+        """
+        return [NOINDEX]
+
+    def page_title(self):
+        """returns a title according to the result set - used for the
+        title in the HTML header
+        """
+        vtitle = self.req.form.get('vtitle')
+        if vtitle:
+            return self.req._(vtitle)
+        # class defined title will only be used if the resulting title doesn't
+        # seem clear enough
+        vtitle = getattr(self, 'title', None) or u''
+        if vtitle:
+            vtitle = self.req._(vtitle)
+        rset = self.rset
+        if rset and rset.rowcount:
+            if rset.rowcount == 1:
+                try:
+                    entity = self.complete_entity(0)
+                    # use long_title to get context information if any
+                    clabel = entity.dc_long_title()
+                except NotAnEntity:
+                    clabel = display_name(self.req, rset.description[0][0])
+                    clabel = u'%s (%s)' % (clabel, vtitle)
+            else :
+                etypes = rset.column_types(0)
+                if len(etypes) == 1:
+                    etype = iter(etypes).next()
+                    clabel = display_name(self.req, etype, 'plural')
+                else :
+                    clabel = u'#[*] (%s)' % vtitle
+        else:
+            clabel = vtitle
+        return u'%s (%s)' % (clabel, self.req.property_value('ui.site-title'))
+
+    def output_url_builder( self, name, url, args ):
+        self.w(u'<script language="JavaScript"><!--\n' \
+               u'function %s( %s ) {\n' % (name, ','.join(args) ) )
+        url_parts = url.split("%s")
+        self.w(u' url="%s"' % url_parts[0] )
+        for arg, part in zip(args, url_parts[1:]):
+            self.w(u'+str(%s)' % arg )
+            if part:
+                self.w(u'+"%s"' % part)
+        self.w('\n document.window.href=url;\n')
+        self.w('}\n-->\n</script>\n')
+
+    def create_url(self, etype, **kwargs):
+        """ return the url of the entity creation form for a given entity type"""
+        return self.req.build_url('add/%s'%etype, **kwargs)
+    
+    def field(self, label, value, row=True, show_label=True, w=None, tr=True):
+        """ read-only field """
+        if w is None:
+            w = self.w
+        if row:
+            w(u'<div class="row">')
+        if show_label:
+            if tr:
+                label = display_name(self.req, label)
+            w(u'<span class="label">%s</span>' % label)
+        w(u'<div class="field">%s</div>' % value)
+        if row:
+            w(u'</div>')
+
+
+# concrete views base classes #################################################
+
+class EntityView(View):
+    """base class for views applying on an entity (i.e. uniform result set)
+    """
+    # XXX deprecate
+    __registerer__ = accepts_registerer
+    __selectors__ = (implements('Any'),)
+    registered = accepts_compat(View.registered.im_func)
+
+    category = 'entityview'
+
+
+class StartupView(View):
+    """base class for views which doesn't need a particular result set
+    to be displayed (so they can always be displayed !)
+    """
+    __registerer__ = priority_registerer
+    __selectors__ = (none_rset,)
+    registered = require_group_compat(View.registered.im_func)
+    
+    category = 'startupview'
+    
+    def url(self):
+        """return the url associated with this view. We can omit rql here"""
+        return self.build_url('view', vid=self.id)
+
+    def html_headers(self):
+        """return a list of html headers (eg something to be inserted between
+        <head> and </head> of the returned page
+
+        by default startup views are indexed
+        """
+        return []
+
+
+class EntityStartupView(EntityView):
+    """base class for entity views which may also be applied to None
+    result set (usually a default rql is provided by the view class)
+    """
+    __selectors__ = ((none_rset | implements('Any')),)
+
+    default_rql = None
+
+    def __init__(self, req, rset):
+        super(EntityStartupView, self).__init__(req, rset)
+        if rset is None:
+            # this instance is not in the "entityview" category
+            self.category = 'startupview'
+
+    def startup_rql(self):
+        """return some rql to be executedif the result set is None"""
+        return self.default_rql
+
+    def call(self, **kwargs):
+        """override call to execute rql returned by the .startup_rql
+        method if necessary
+        """
+        if self.rset is None:
+            self.rset = self.req.execute(self.startup_rql())
+        rset = self.rset
+        for i in xrange(len(rset)):
+            self.wview(self.id, rset, row=i, **kwargs)
+
+    def url(self):
+        """return the url associated with this view. We can omit rql if we
+        are on a result set on which we do not apply.
+        """
+        if not self.__select__(self.req, self.rset):
+            return self.build_url(vid=self.id)
+        return super(EntityStartupView, self).url()
+
+
+class AnyRsetView(View):
+    """base class for views applying on any non empty result sets"""
+    __selectors__ = (nonempty_rset,)
+
+    category = 'anyrsetview'
+
+    def columns_labels(self, tr=True):
+        if tr:
+            translate = display_name
+        else:
+            translate = lambda req, val: val
+        rqlstdescr = self.rset.syntax_tree().get_description()[0] # XXX missing Union support
+        labels = []
+        for colindex, attr in enumerate(rqlstdescr):
+            # compute column header
+            if colindex == 0 or attr == 'Any': # find a better label
+                label = ','.join(translate(self.req, et)
+                                 for et in self.rset.column_types(colindex))
+            else:
+                label = translate(self.req, attr)
+            labels.append(label)
+        return labels
+
+    
+# concrete template base classes ##############################################
+
+class Template(View):
+    """a template is almost like a view, except that by default a template
+    is only used globally (i.e. no result set adaptation)
+    """
+    __registry__ = 'templates'
+    __selectors__ = (yes,)
+
+    registered = require_group_compat(View.registered.im_func)
+
+    def template(self, oid, **kwargs):
+        """shortcut to self.registry.render method on the templates registry"""
+        w = kwargs.pop('w', self.w)
+        self.vreg.render('templates', oid, self.req, w=w, **kwargs)
+
+
+class MainTemplate(Template):
+    """main template are primary access point to render a full HTML page.
+    There is usually at least a regular main template and a simple fallback
+    one to display error if the first one failed
+    """
+    base_doctype = STRICT_DOCTYPE
+
+    @property
+    def doctype(self):
+        if self.req.xhtml_browser():
+            return self.base_doctype % CW_XHTML_EXTENSIONS
+        return self.base_doctype % ''
+
+    def set_stream(self, w=None, templatable=True):
+        if templatable and self.w is not None:
+            return
+
+        if w is None:
+            if self.binary:
+                self._stream = stream = StringIO()
+            elif not templatable:
+                # not templatable means we're using a non-html view, we don't
+                # want the HTMLStream stuff to interfere during data generation
+                self._stream = stream = UStringIO()
+            else:
+                self._stream = stream = HTMLStream(self.req)
+            w = stream.write
+        else:
+            stream = None
+        self.w = w
+        return stream
+
+    def write_doctype(self, xmldecl=True):
+        assert isinstance(self._stream, HTMLStream)
+        self._stream.doctype = self.doctype
+        if not xmldecl:
+            self._stream.xmldecl = u''
+
+# concrete component base classes #############################################
+
+class ReloadableMixIn(object):
+    """simple mixin for reloadable parts of UI"""
+    
+    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.req.add_js('cubicweb.ajax.js')
+        if nonify:
+            _cb = cb
+            def cb(*args):
+                _cb(*args)
+        cbname = self.req.register_onetime_callback(cb, *args)
+        return self.build_js(cbname, html_escape(msg or ''))
+        
+    def build_update_js_call(self, cbname, msg):
+        rql = html_escape(self.rset.printable_rql())
+        return "javascript:userCallbackThenUpdateUI('%s', '%s', '%s', '%s', '%s', '%s')" % (
+            cbname, self.id, rql, msg, self.__registry__, self.div_id())
+    
+    def build_reload_js_call(self, cbname, msg):
+        return "javascript:userCallbackThenReloadPage('%s', '%s')" % (cbname, msg)
+
+    build_js = build_update_js_call # expect updatable component by default
+    
+    def div_id(self):
+        return ''
+
+
+class Component(ReloadableMixIn, View):
+    """base class for components"""
+    __registry__ = 'components'
+    __registerer__ = yes_registerer
+    __selectors__ = (yes,)
+    property_defs = {
+        _('visible'):  dict(type='Boolean', default=True,
+                            help=_('display the box or not')),
+        }    
+
+    def div_class(self):
+        return '%s %s' % (self.propval('htmlclass'), self.id)
+
+    def div_id(self):
+        return '%sComponent' % self.id