merge tls-sprint
authorFlorent <florent@secondweb.fr>
Wed, 29 Apr 2009 17:36:49 +0200
branchtls-sprint
changeset 1544 d8fb60c56d69
parent 1543 dca9817bb337 (current diff)
parent 1541 ddddbb748355 (diff)
child 1545 53d3d783370f
merge
web/views/startup.py
--- a/appobject.py	Wed Apr 29 11:06:13 2009 +0200
+++ b/appobject.py	Wed Apr 29 17:36:49 2009 +0200
@@ -23,21 +23,21 @@
 
 ONESECOND = timedelta(0, 1, 0)
 
-class Cache(dict):    
+class Cache(dict):
     def __init__(self):
         super(Cache, self).__init__()
         self.cache_creation_date = None
         self.latest_cache_lookup = datetime.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
@@ -62,11 +62,11 @@
         cls.config = vreg.config
         cls.register_properties()
         return cls
-    
+
     @classmethod
     def vreg_initialization_completed(cls):
         pass
-    
+
     @classmethod
     def selected(cls, *args, **kwargs):
         """by default web app objects are usually instantiated on
@@ -85,9 +85,9 @@
     # 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():
@@ -95,7 +95,7 @@
             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)
@@ -109,7 +109,7 @@
         if not isinstance(selector, tuple):
             selector = (selector,)
         return selector
-    
+
     def __init__(self, req=None, rset=None, row=None, col=None, **extra):
         super(AppRsetObject, self).__init__()
         self.req = req
@@ -117,12 +117,12 @@
         self.row = row
         self.col = col
         self.extra_kwargs = extra
-        
+
     def get_cache(self, cachename):
         """
         NOTE: cachename should be dotted names as in :
         - cubicweb.mycache
-        - cubes.blog.mycache 
+        - cubes.blog.mycache
         - etc.
         """
         if cachename in CACHE_REGISTRY:
@@ -132,7 +132,7 @@
             CACHE_REGISTRY[cachename] = cache
         _now = datetime.now()
         if _now > cache.latest_cache_lookup + ONESECOND:
-            ecache = self.req.execute('Any C,T WHERE C is CWCache, C name %(name)s, C timestamp T', 
+            ecache = self.req.execute('Any C,T WHERE C is CWCache, 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):
@@ -143,7 +143,7 @@
     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
@@ -165,7 +165,7 @@
         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:
@@ -187,7 +187,7 @@
             rql = rqlst.as_string(kwargs=self.rset.args)
             rqlst.parent = None
         return rql
-        
+
     def view(self, __vid, rset=None, __fallback_vid=None, **kwargs):
         """shortcut to self.vreg.render method avoiding to pass self.req"""
         try:
@@ -197,11 +197,11 @@
                 raise
             view = self.vreg.select_view(__fallback_vid, self.req, rset, **kwargs)
         return view.dispatch(**kwargs)
-    
+
     # 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
@@ -217,13 +217,13 @@
         return self.req.build_url(method, **kwargs)
 
     # various resources accessors #############################################
-    
+
     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)
@@ -239,14 +239,14 @@
         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')
         cbname = self.req.register_onetime_callback(cb, *args)
-        msg = dumps(msg or '') 
+        msg = dumps(msg or '')
         return "javascript:userCallbackThenReloadPage('%s', %s)" % (
             cbname, msg)
 
@@ -293,23 +293,23 @@
         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
--- a/dbapi.py	Wed Apr 29 11:06:13 2009 +0200
+++ b/dbapi.py	Wed Apr 29 17:36:49 2009 +0200
@@ -16,7 +16,7 @@
 from cubicweb import ConnectionError, RequestSessionMixIn, set_log_methods
 from cubicweb.cwvreg import CubicWebRegistry, MulCnxCubicWebRegistry
 from cubicweb.cwconfig import CubicWebNoAppConfiguration
-        
+
 _MARKER = object()
 
 class ConnectionProperties(object):
@@ -29,7 +29,7 @@
 
 def get_repository(method, database=None, config=None, vreg=None):
     """get a proxy object to the CubicWeb repository, using a specific RPC method.
-     
+
     Only 'in-memory' and 'pyro' are supported for now. Either vreg or config
     argument should be given
     """
@@ -60,10 +60,10 @@
                                   'you may have to restart your server-side '
                                   'application' % nsid)
         return core.getProxyForURI(uri)
-        
+
 def repo_connect(repo, user, password, cnxprops=None):
     """Constructor to create a new connection to the CubicWeb repository.
-    
+
     Returns a Connection instance.
     """
     cnxprops = cnxprops or ConnectionProperties('inmemory')
@@ -72,7 +72,7 @@
     if cnxprops.cnxtype == 'inmemory':
         cnx.vreg = repo.vreg
     return cnx
-    
+
 def connect(database=None, user=None, password=None, host=None,
             group=None, cnxprops=None, port=None, setvreg=True, mulcnx=True,
             initlog=True):
@@ -110,7 +110,7 @@
 
 def in_memory_cnx(config, user, password):
     """usefull method for testing and scripting to get a dbapi.Connection
-    object connected to an in-memory repository instance 
+    object connected to an in-memory repository instance
     """
     if isinstance(config, CubicWebRegistry):
         vreg = config
@@ -126,7 +126,7 @@
 
 
 class DBAPIRequest(RequestSessionMixIn):
-    
+
     def __init__(self, vreg, cnx=None):
         super(DBAPIRequest, self).__init__(vreg)
         try:
@@ -146,10 +146,10 @@
 
     def base_url(self):
         return self.vreg.config['base-url']
-    
+
     def from_controller(self):
         return 'view'
-    
+
     def set_connection(self, cnx, user=None):
         """method called by the session handler when the user is authenticated
         or an anonymous connection is open
@@ -157,7 +157,7 @@
         self.cnx = cnx
         self.cursor = cnx.cursor(self)
         self.set_user(user)
-    
+
     def set_default_language(self, vreg):
         try:
             self.lang = vreg.property_value('ui.language')
@@ -175,26 +175,26 @@
         rset.vreg = self.vreg
         rset.req = self
         return rset
-    
+
     def describe(self, eid):
         """return a tuple (type, sourceuri, extid) for the entity with id <eid>"""
         return self.cnx.describe(eid)
-    
+
     def source_defs(self):
         """return the definition of sources used by the repository."""
         return self.cnx.source_defs()
-            
+
     # entities cache management ###############################################
-    
+
     def entity_cache(self, eid):
         return self._eid_cache[eid]
-    
+
     def set_entity_cache(self, entity):
         self._eid_cache[entity.eid] = entity
 
     def cached_entities(self):
         return self._eid_cache.values()
-    
+
     def drop_entity_cache(self, eid=None):
         if eid is None:
             self._eid_cache = {}
@@ -210,11 +210,11 @@
     def get_session_data(self, key, default=None, pop=False):
         """return value associated to `key` in session data"""
         return self.cnx.get_session_data(key, default, pop)
-        
+
     def set_session_data(self, key, value):
         """set value associated to `key` in session data"""
         return self.cnx.set_session_data(key, value)
-        
+
     def del_session_data(self, key):
         """remove value associated to `key` in session data"""
         return self.cnx.del_session_data(key)
@@ -222,7 +222,7 @@
     def get_shared_data(self, key, default=None, pop=False):
         """return value associated to `key` in shared data"""
         return self.cnx.get_shared_data(key, default, pop)
-        
+
     def set_shared_data(self, key, value, querydata=False):
         """set value associated to `key` in shared data
 
@@ -245,14 +245,14 @@
         self._user = user
         if user:
             self.set_entity_cache(user)
-        
+
     def execute(self, *args, **kwargs):
         """Session interface compatibility"""
         return self.cursor.execute(*args, **kwargs)
 
 set_log_methods(DBAPIRequest, getLogger('cubicweb.dbapi'))
-        
-        
+
+
 # exceptions ##################################################################
 
 class ProgrammingError(Exception): #DatabaseError):
@@ -288,15 +288,15 @@
 """String constant stating the type of parameter marker formatting expected by
 the interface. Possible values are :
 
-                'qmark'         Question mark style, 
+                'qmark'         Question mark style,
                                 e.g. '...WHERE name=?'
-                'numeric'       Numeric, positional style, 
+                'numeric'       Numeric, positional style,
                                 e.g. '...WHERE name=:1'
-                'named'         Named style, 
+                'named'         Named style,
                                 e.g. '...WHERE name=:name'
-                'format'        ANSI C printf format codes, 
+                'format'        ANSI C printf format codes,
                                 e.g. '...WHERE name=%s'
-                'pyformat'      Python extended format codes, 
+                'pyformat'      Python extended format codes,
                                 e.g. '...WHERE name=%(name)s'
 """
 paramstyle = 'pyformat'
@@ -333,28 +333,28 @@
 
     def request(self):
         return DBAPIRequest(self.vreg, self)
-    
+
     def session_data(self):
         """return a dictionnary containing session data"""
         return self.data
-        
+
     def get_session_data(self, key, default=None, pop=False):
         """return value associated to `key` in session data"""
         if pop:
             return self.data.pop(key, default)
         else:
             return self.data.get(key, default)
-        
+
     def set_session_data(self, key, value):
         """set value associated to `key` in session data"""
         self.data[key] = value
-        
+
     def del_session_data(self, key):
         """remove value associated to `key` in session data"""
         try:
             del self.data[key]
         except KeyError:
-            pass    
+            pass
 
     def check(self):
         """raise `BadSessionId` if the connection is no more valid"""
@@ -363,7 +363,7 @@
     def get_shared_data(self, key, default=None, pop=False):
         """return value associated to `key` in shared data"""
         return self._repo.get_shared_data(self.sessionid, key, default, pop)
-        
+
     def set_shared_data(self, key, value, querydata=False):
         """set value associated to `key` in shared data
 
@@ -373,10 +373,10 @@
         repository side.
         """
         return self._repo.set_shared_data(self.sessionid, key, value, querydata)
-        
+
     def get_schema(self):
         """Return the schema currently used by the repository.
-        
+
         This is NOT part of the DB-API.
         """
         if self._closed is not None:
@@ -414,10 +414,10 @@
             # application specific hooks
             if self._repo.config.application_hooks:
                 hm.register_hooks(config.load_hooks(self.vreg))
-            
+
     def source_defs(self):
         """Return the definition of sources used by the repository.
-        
+
         This is NOT part of the DB-API.
         """
         if self._closed is not None:
@@ -443,13 +443,13 @@
                 self.close()
             except:
                 pass
-    
+
     def describe(self, eid):
         return self._repo.describe(self.sessionid, eid)
-            
+
     def close(self):
         """Close the connection now (rather than whenever __del__ is called).
-        
+
         The connection will be unusable from this point forward; an Error (or
         subclass) exception will be raised if any operation is attempted with
         the connection. The same applies to all cursor objects trying to use the
@@ -465,7 +465,7 @@
         """Commit any pending transaction to the database. Note that if the
         database supports an auto-commit feature, this must be initially off. An
         interface method may be provided to turn it back on.
-            
+
         Database modules that do not support transactions should implement this
         method with void functionality.
         """
@@ -476,7 +476,7 @@
     def rollback(self):
         """This method is optional since not all databases provide transaction
         support.
-            
+
         In case a database does provide transactions this method causes the the
         database to roll back to the start of any pending transaction.  Closing
         a connection without committing the changes first will cause an implicit
@@ -510,7 +510,7 @@
     support is implemented (see also the connection's rollback() and commit()
     methods.)
     """
-    
+
     def __init__(self, connection, repo, req=None):
         """This read-only attribute return a reference to the Connection
         object on which the cursor was created.
@@ -522,7 +522,7 @@
         """This read/write attribute specifies the number of rows to fetch at a
         time with fetchmany(). It defaults to 1 meaning to fetch a single row
         at a time.
-        
+
         Implementations must observe this value with respect to the fetchmany()
         method, but are free to interact with the database a single row at a
         time. It may also be used in the implementation of executemany().
@@ -535,7 +535,7 @@
         self._closed = None
         self._index = 0
 
-        
+
     def close(self):
         """Close the cursor now (rather than whenever __del__ is called).  The
         cursor will be unusable from this point forward; an Error (or subclass)
@@ -543,30 +543,30 @@
         """
         self._closed = True
 
-            
+
     def execute(self, operation, parameters=None, eid_key=None, build_descr=True):
         """Prepare and execute a database operation (query or command).
         Parameters may be provided as sequence or mapping and will be bound to
         variables in the operation.  Variables are specified in a
         database-specific notation (see the module's paramstyle attribute for
         details).
-        
+
         A reference to the operation will be retained by the cursor.  If the
         same operation object is passed in again, then the cursor can optimize
         its behavior.  This is most effective for algorithms where the same
         operation is used, but different parameters are bound to it (many
         times).
-        
+
         For maximum efficiency when reusing an operation, it is best to use the
         setinputsizes() method to specify the parameter types and sizes ahead
         of time.  It is legal for a parameter to not match the predefined
         information; the implementation should compensate, possibly with a loss
         of efficiency.
-        
+
         The parameters may also be specified as list of tuples to e.g. insert
         multiple rows in a single operation, but this kind of usage is
         depreciated: executemany() should be used instead.
-        
+
         Return values are not defined by the DB-API, but this here it returns a
         ResultSet object.
         """
@@ -575,25 +575,25 @@
         self.req.decorate_rset(res)
         self._index = 0
         return res
-        
+
 
     def executemany(self, operation, seq_of_parameters):
         """Prepare a database operation (query or command) and then execute it
         against all parameter sequences or mappings found in the sequence
         seq_of_parameters.
-        
+
         Modules are free to implement this method using multiple calls to the
         execute() method or by using array operations to have the database
         process the sequence as a whole in one call.
-        
+
         Use of this method for an operation which produces one or more result
         sets constitutes undefined behavior, and the implementation is
         permitted (but not required) to raise an exception when it detects that
         a result set has been created by an invocation of the operation.
-        
+
         The same comments as for execute() also apply accordingly to this
         method.
-        
+
         Return values are not defined.
         """
         for parameters in seq_of_parameters:
@@ -606,7 +606,7 @@
     def fetchone(self):
         """Fetch the next row of a query result set, returning a single
         sequence, or None when no more data is available.
-        
+
         An Error (or subclass) exception is raised if the previous call to
         execute*() did not produce any result set or no call was issued yet.
         """
@@ -616,21 +616,21 @@
         self._index += 1
         return row
 
-        
+
     def fetchmany(self, size=None):
         """Fetch the next set of rows of a query result, returning a sequence
         of sequences (e.g. a list of tuples). An empty sequence is returned
         when no more rows are available.
-        
+
         The number of rows to fetch per call is specified by the parameter.  If
         it is not given, the cursor's arraysize determines the number of rows
         to be fetched. The method should try to fetch as many rows as indicated
         by the size parameter. If this is not possible due to the specified
         number of rows not being available, fewer rows may be returned.
-        
+
         An Error (or subclass) exception is raised if the previous call to
         execute*() did not produce any result set or no call was issued yet.
-        
+
         Note there are performance considerations involved with the size
         parameter.  For optimal performance, it is usually best to use the
         arraysize attribute.  If the size parameter is used, then it is best
@@ -644,12 +644,12 @@
         self._index += size
         return rows
 
-        
+
     def fetchall(self):
         """Fetch all (remaining) rows of a query result, returning them as a
         sequence of sequences (e.g. a list of tuples).  Note that the cursor's
         arraysize attribute can affect the performance of this operation.
-        
+
         An Error (or subclass) exception is raised if the previous call to
         execute*() did not produce any result set or no call was issued yet.
         """
@@ -665,39 +665,39 @@
     def setinputsizes(self, sizes):
         """This can be used before a call to execute*() to predefine memory
         areas for the operation's parameters.
-        
+
         sizes is specified as a sequence -- one item for each input parameter.
         The item should be a Type Object that corresponds to the input that
         will be used, or it should be an integer specifying the maximum length
         of a string parameter.  If the item is None, then no predefined memory
         area will be reserved for that column (this is useful to avoid
         predefined areas for large inputs).
-        
+
         This method would be used before the execute*() method is invoked.
-        
+
         Implementations are free to have this method do nothing and users are
         free to not use it.
         """
         pass
 
-        
+
     def setoutputsize(self, size, column=None):
         """Set a column buffer size for fetches of large columns (e.g. LONGs,
         BLOBs, etc.).  The column is specified as an index into the result
         sequence.  Not specifying the column will set the default size for all
         large columns in the cursor.
-        
+
         This method would be used before the execute*() method is invoked.
-        
+
         Implementations are free to have this method do nothing and users are
         free to not use it.
-        """    
+        """
         pass
 
-    
+
 class LogCursor(Cursor):
     """override the standard cursor to log executed queries"""
-    
+
     def execute(self, operation, parameters=None, eid_key=None, build_descr=True):
         """override the standard cursor to log executed queries"""
         tstart, cstart = time(), clock()
--- a/entity.py	Wed Apr 29 11:06:13 2009 +0200
+++ b/entity.py	Wed Apr 29 17:36:49 2009 +0200
@@ -51,11 +51,11 @@
     def _dispatch_rtags(tags, rtype, role, stype, otype):
         for tag in tags:
             if tag in _MODE_TAGS:
-                uicfg.rmode.set_rtag(tag, rtype, role, stype, otype)
+                uicfg.rmode.tag_relation(tag, (stype, rtype, otype), role)
             elif tag in _CATEGORY_TAGS:
-                uicfg.rcategories.set_rtag(tag, rtype, role, stype, otype)
+                uicfg.rcategories.tag_relation(tag, (stype, rtype, otype), role)
             elif tag == 'inlineview':
-                uicfg.rinlined.set_rtag(True, rtype, role, stype, otype)
+                uicfg.rinlined.tag_relation(True, (stype, rtype, otype), role)
             else:
                 raise ValueError(tag)
 
@@ -127,7 +127,8 @@
                     if wdgname == 'StringWidget':
                         wdgname = 'TextInput'
                     widget = getattr(formwidgets, wdgname)
-                    AutomaticEntityForm.rwidgets.set_rtag(wdgname, rtype, 'subject', etype)
+                    AutomaticEntityForm.rwidgets.tag_relation(
+                        wdgname, (etype, rtype, '*'), 'subject')
         return super(_metaentity, mcs).__new__(mcs, name, bases, classdict)
 
 
--- a/rtags.py	Wed Apr 29 11:06:13 2009 +0200
+++ b/rtags.py	Wed Apr 29 17:36:49 2009 +0200
@@ -6,85 +6,81 @@
 """
 __docformat__ = "restructuredtext en"
 
+
 class RelationTags(object):
-    """RelationTags instances are a tag store for full relation definitions :
+    """a tag store for full relation definitions :
 
-         (subject type, relation type, object type, role)
+         (subject type, relation type, object type, tagged)
 
     allowing to set tags using wildcard (eg '*') as subject type / object type
 
-    if `use_set` is True, a set of tags is associated to each key, and you
-    should use rtags / etype_rtags / add_rtag api. Otherwise, a single tag is
-    associated to each key, and you should use rtag / etype_rtag / set_rtag api.
+    This class associates a single tag to each key.
     """
 
-    def __init__(self, use_set=False):
-        self.use_set = use_set
+    def __init__(self):
         self._tagdefs = {}
 
-    def set_rtag(self, tag, rtype, role, stype='*', otype='*'):
-        assert not self.use_set
-        assert role in ('subject', 'object'), role
-        self._tagdefs[(str(rtype), role, str(stype), str(otype))] = tag
+    def __repr__(self):
+        return repr(self._tagdefs)
+
+    # dict compat
+    def __getitem__(self, key):
+        return self.get(*key)
+    __contains__ = __getitem__
 
-    def del_rtag(self, rtype, role, stype='*', otype='*'):
-        assert not self.use_set
-        assert role in ('subject', 'object'), role
-        del self._tagdefs[(str(rtype), role, str(stype), str(otype))]
+    def _get_keys(self, rtype, tagged, stype, otype):
+        assert tagged in ('subject', 'object'), tagged
+        keys = [(rtype, tagged, '*', '*'),
+                (rtype, tagged, '*', otype),
+                (rtype, tagged, stype, '*'),
+                (rtype, tagged, stype, otype)]
+        if stype == '*' or otype == '*':
+            keys.remove((rtype, tagged, '*', '*'))
+            if stype == '*':
+                keys.remove((rtype, tagged, '*', otype))
+            if otype == '*':
+                keys.remove((rtype, tagged, stype, '*'))
+        return keys
 
-    def rtag(self, rtype, role, stype='*', otype='*'):
-        assert not self.use_set
-        for key in reversed(self._get_keys(rtype, role, stype, otype)):
+    def tag_relation(self, tag, relation, tagged):
+        assert tagged in ('subject', 'object'), tagged
+        stype, rtype, otype = relation
+        self._tagdefs[(str(rtype), tagged, str(stype), str(otype))] = tag
+
+    def del_rtag(self, relation, tagged):
+        assert tagged in ('subject', 'object'), tagged
+        stype, rtype, otype = relation
+        del self._tagdefs[(str(rtype), tagged, str(stype), str(otype))]
+
+    def get(self, rtype, tagged, stype='*', otype='*'):
+        for key in reversed(self._get_keys(rtype, tagged, stype, otype)):
             try:
                 return self._tagdefs[key]
             except KeyError:
                 continue
         return None
 
-    def etype_rtag(self, etype, rtype, role, ttype='*'):
-        if role == 'subject':
-            return self.rtag(rtype, role, etype, ttype)
-        return self.rtag(rtype, role, ttype, etype)
+    def etype_get(self, etype, rtype, tagged, ttype='*'):
+        if tagged == 'subject':
+            return self.get(rtype, tagged, etype, ttype)
+        return self.get(rtype, tagged, ttype, etype)
+
+
 
-    def add_rtag(self, tag, rtype, role, stype='*', otype='*'):
-        assert self.use_set
-        assert role in ('subject', 'object'), role
-        rtags = self._tagdefs.setdefault((rtype, role, stype, otype), set())
+class RelationTagsSet(RelationTags):
+    """This class associates a set of tags to each key."""
+
+    def tag_relation(self, tag, relation, tagged):
+        assert tagged in ('subject', 'object'), tagged
+        stype, rtype, otype = relation
+        rtags = self._tagdefs.setdefault((rtype, tagged, stype, otype), set())
         rtags.add(tag)
 
-    def rtags(self, rtype, role, stype='*', otype='*'):
-        assert self.use_set
+    def get(self, rtype, tagged, stype='*', otype='*'):
         rtags = set()
-        for key in self._get_keys(rtype, role, stype, otype):
+        for key in self._get_keys(rtype, tagged, stype, otype):
             try:
                 rtags.update(self._tagdefs[key])
             except KeyError:
                 continue
         return rtags
-
-    def etype_rtags(self, etype, rtype, role, ttype='*'):
-        if role == 'subject':
-            return self.rtags(rtype, role, etype, ttype)
-        return self.rtags(rtype, role, ttype, etype)
-
-    def _get_keys(self, rtype, role, stype, otype):
-        assert role in ('subject', 'object'), role
-        keys = [(rtype, role, '*', '*'),
-                (rtype, role, '*', otype),
-                (rtype, role, stype, '*'),
-                (rtype, role, stype, otype)]
-        if stype == '*' or otype == '*':
-            keys.remove((rtype, role, '*', '*'))
-            if stype == '*':
-                keys.remove((rtype, role, '*', otype))
-            if otype == '*':
-                keys.remove((rtype, role, stype, '*'))
-        return keys
-
-    # dict compat
-    def __getitem__(self, key):
-        if isinstance(key, basestring):
-            key = (key,)
-        return self.rtags(*key)
-
-    __contains__ = __getitem__
--- a/test/unittest_rtags.py	Wed Apr 29 11:06:13 2009 +0200
+++ b/test/unittest_rtags.py	Wed Apr 29 17:36:49 2009 +0200
@@ -1,19 +1,19 @@
 from logilab.common.testlib import TestCase, unittest_main
-from cubicweb.rtags import RelationTags
+from cubicweb.rtags import RelationTags, RelationTagsSet
 
 class RelationTagsTC(TestCase):
-    
+
     def test_rtags_expansion(self):
         rtags = RelationTags()
-        rtags.set_rtag('primary', 'travaille', 'subject', 'Societe')
-        rtags.set_rtag('secondary', 'evaluee', 'subject')
-        rtags.set_rtag('generated', 'tags', 'object')
-        self.assertEquals(rtags.rtag('evaluee', 'subject', 'Note'), 'secondary')
-        self.assertEquals(rtags.rtag('travaille', 'subject', 'Societe'), 'primary')
-        self.assertEquals(rtags.rtag('travaille', 'subject', 'Note'), None)
-        self.assertEquals(rtags.rtag('tags', 'subject', 'Note'), None)
-        self.assertEquals(rtags.rtag('tags', 'object', 'Note'), 'generated')
-        
+        rtags.tag_relation('primary', ('Societe', 'travaille', '*'), 'subject', )
+        rtags.tag_relation('secondary', ('*', 'evaluee', '*'), 'subject')
+        rtags.tag_relation('generated', ('*', 'tags', '*'), 'object')        
+        self.assertEquals(rtags.get('evaluee', 'subject', 'Note'), 'secondary')
+        self.assertEquals(rtags.get('travaille', 'subject', 'Societe'), 'primary')
+        self.assertEquals(rtags.get('travaille', 'subject', 'Note'), None)
+        self.assertEquals(rtags.get('tags', 'subject', 'Note'), None)
+        self.assertEquals(rtags.get('tags', 'object', 'Note'), 'generated')
+
 #         self.assertEquals(rtags.rtag('evaluee', 'Note', 'subject'), set(('secondary', 'link')))
 #         self.assertEquals(rtags.is_inlined('evaluee', 'Note', 'subject'), False)
 #         self.assertEquals(rtags.rtag('evaluee', 'Personne', 'subject'), set(('secondary', 'link')))
@@ -30,7 +30,16 @@
 #         self.assertEquals(rtags.rtag('evaluee', 'Note', 'subject'), set(('inlineview', 'link')))
 #         self.assertEquals(rtags.is_inlined('evaluee', 'Note', 'subject'), True)
 #         self.assertEquals(rtags.rtag('evaluee', 'Personne', 'subject'), set(('secondary', 'link')))
-#         self.assertEquals(rtags.is_inlined('evaluee', 'Personne', 'subject'), False)        
+#         self.assertEquals(rtags.is_inlined('evaluee', 'Personne', 'subject'), False)
+
+
+    def test_rtagset_expansion(self):
+        rtags = RelationTagsSet()
+        rtags.tag_relation('primary', ('Societe', 'travaille', '*'), 'subject', )
+        rtags.tag_relation('secondary', ('*', 'travaille', '*'), 'subject')
+        self.assertEquals(rtags.get('travaille', 'subject', 'Societe'), set(('primary', 'secondary')))
+        self.assertEquals(rtags.get('travaille', 'subject', 'Note'), set(('secondary',)))
+        self.assertEquals(rtags.get('tags', 'subject', 'Note'), set())
 
 if __name__ == '__main__':
     unittest_main()
--- a/web/action.py	Wed Apr 29 11:06:13 2009 +0200
+++ b/web/action.py	Wed Apr 29 17:36:49 2009 +0200
@@ -20,7 +20,7 @@
     request search state.
     """
     __registry__ = 'actions'
-    __select__ = yes()
+    __select__ = match_search_state('normal')
 
     property_defs = {
         'visible':  dict(type='Boolean', default=True,
--- a/web/form.py	Wed Apr 29 11:06:13 2009 +0200
+++ b/web/form.py	Wed Apr 29 17:36:49 2009 +0200
@@ -58,13 +58,22 @@
             self.req.set_page_data('rql_varmaker', varmaker)
         self.varmaker = varmaker
 
+    def session_key(self):
+        """return the key that may be used to store / retreive data about a
+        previous post which failed because of a validation error
+        """
+        return '%s#%s' % (self.req.url(), self.domid)
+
     def __init__(self, req, rset, **kwargs):
         super(FormMixIn, self).__init__(req, rset, **kwargs)
+        self.restore_previous_post(self.session_key())
+
+    def restore_previous_post(self, sessionkey):
         # get validation session data which may have been previously set.
         # deleting validation errors here breaks form reloading (errors are
         # no more available), they have to be deleted by application's publish
         # method on successful commit
-        forminfo = req.get_session_data(req.url())
+        forminfo = self.req.get_session_data(sessionkey, pop=True)
         if forminfo:
             req.data['formvalues'] = forminfo['values']
             req.data['formerrors'] = errex = forminfo['errors']
@@ -240,7 +249,7 @@
                 assert hasattr(self.__class__, key) and not key[0] == '_', key
                 setattr(self, key, val)
         if self.set_error_url:
-            self.form_add_hidden('__errorurl', req.url())
+            self.form_add_hidden('__errorurl', self.session_key())
         if self.copy_nav_params:
             for param in NAV_FORM_PARAMETERS:
                 if not param in kwargs:
@@ -250,6 +259,8 @@
         if submitmsg is not None:
             self.form_add_hidden('__message', submitmsg)
         self.context = None
+        if 'domid' in kwargs:# session key changed
+            self.restore_previous_post(self.session_key())
 
     @iclassmethod
     def field_by_name(cls_or_self, name, role='subject'):
@@ -470,7 +481,7 @@
         """overriden to add edit[s|o] hidden fields and to ensure schema fields
         have eidparam set to True
 
-        edit[s|o] hidden fields are used t o indicate the value for the
+        edit[s|o] hidden fields are used to indicate the value for the
         associated field before the (potential) modification made when
         submitting the form.
         """
@@ -589,7 +600,7 @@
         """
         entity = self.edited_entity
         if isinstance(rtype, basestring):
-            rtype = self.schema.rschema(rtype)
+            rtype = entity.schema.rschema(rtype)
         done = None
         assert not rtype.is_final(), rtype
         if entity.has_eid():
@@ -611,7 +622,7 @@
         """
         entity = self.edited_entity
         if isinstance(rtype, basestring):
-            rtype = self.schema.rschema(rtype)
+            rtype = entity.schema.rschema(rtype)
         done = None
         if entity.has_eid():
             done = set(e.eid for e in getattr(entity, 'reverse_%s' % rtype))
--- a/web/formwidgets.py	Wed Apr 29 11:06:13 2009 +0200
+++ b/web/formwidgets.py	Wed Apr 29 17:36:49 2009 +0200
@@ -159,16 +159,20 @@
 
 class Select(FieldWidget):
     """<select>, for field having a specific vocabulary"""
-    def __init__(self, attrs=None, multiple=False):
+    def __init__(self, attrs=None, multiple=False, sort=False):
         super(Select, self).__init__(attrs)
-        self.multiple = multiple
+        self._multiple = multiple
+        self._sort = sort
 
     def render(self, form, field):
         name, curvalues, attrs = self._render_attrs(form, field)
-        if not 'size' in attrs:
+        if not 'size' in attrs and self._multiple:
             attrs['size'] = '5'
         options = []
-        for label, value in field.vocabulary(form):
+        vocab = field.vocabulary(form)
+        if self._sort:
+            vocab = sorted(vocab)
+        for label, value in vocab:
             if value is None:
                 # handle separator
                 options.append(u'<optgroup label="%s"/>' % (label or ''))
@@ -176,7 +180,7 @@
                 options.append(tags.option(label, value=value, selected='selected'))
             else:
                 options.append(tags.option(label, value=value))
-        return tags.select(name=name, multiple=self.multiple,
+        return tags.select(name=name, multiple=self._multiple,
                            options=options, **attrs)
 
 
--- a/web/test/unittest_views_editforms.py	Wed Apr 29 11:06:13 2009 +0200
+++ b/web/test/unittest_views_editforms.py	Wed Apr 29 17:36:49 2009 +0200
@@ -9,13 +9,14 @@
 class AutomaticEntityFormTC(EnvBasedTC):
 
     def test_custom_widget(self):
-        AEF.rwidgets.set_rtag(AutoCompletionWidget, 'login', 'subject', 'CWUser')
+        AEF.rwidgets.tag_relation(AutoCompletionWidget,
+                                  ('CWUser', 'login', '*'), 'subject')
         form = self.vreg.select_object('forms', 'edition', self.request(), None,
                                        entity=self.user())
         field = form.field_by_name('login')
         self.assertIsInstance(field.widget, AutoCompletionWidget)
         AEF.rwidgets.del_rtag('login', 'subject', 'CWUser')
-        
+
 
     def test_euser_relations_by_category(self):
         #for (rtype, role, stype, otype), tag in AEF.rcategories._tagdefs.items():
@@ -41,7 +42,7 @@
                                ('modification_date', 'subject'),
                                ('owned_by', 'subject'),
                                ('bookmarked_by', 'object'),
-                               ])        
+                               ])
         self.assertListEquals(rbc(e, 'generic'),
                               [('primary_email', 'subject'),
                                ('use_email', 'subject'),
@@ -63,9 +64,9 @@
                                ])
 
     def test_inlined_view(self):
-        self.failUnless(AEF.rinlined.etype_rtag('CWUser', 'use_email', 'subject'))
-        self.failIf(AEF.rinlined.etype_rtag('CWUser', 'primary_email', 'subject'))
-        
+        self.failUnless(AEF.rinlined.etype_get('CWUser', 'use_email', 'subject'))
+        self.failIf(AEF.rinlined.etype_get('CWUser', 'primary_email', 'subject'))
+
     def test_personne_relations_by_category(self):
         e = self.etype_instance('Personne')
         self.assertListEquals(rbc(e, 'primary'),
@@ -91,7 +92,7 @@
                                ('creation_date', 'subject'),
                                ('modification_date', 'subject'),
                                ('owned_by', 'subject'),
-                               ])        
+                               ])
         self.assertListEquals(rbc(e, 'generic'),
                               [('travaille', 'subject'),
                                ('connait', 'object')
@@ -103,7 +104,7 @@
                                ('is_instance_of', 'subject'),
                                ('identity', 'object'),
                                ])
-        
+
     def test_edition_form(self):
         rset = self.execute('CWUser X LIMIT 1')
         form = self.vreg.select_object('forms', 'edition', rset.req, rset,
@@ -112,41 +113,41 @@
         self.vreg.select_object('forms', 'edition', self.request(), None,
                                 entity=rset.get_entity(0, 0))
         self.failIf(any(f for f in form.fields if f is None))
-        
-        
+
+
 class FormViewsTC(WebTest):
     def test_delete_conf_formview(self):
         rset = self.execute('CWGroup X')
         self.view('deleteconf', rset, template=None).source
-        
+
     def test_automatic_edition_formview(self):
         rset = self.execute('CWUser X')
         self.view('edition', rset, row=0, template=None).source
-        
+
     def test_automatic_edition_formview(self):
         rset = self.execute('CWUser X')
         self.view('copy', rset, row=0, template=None).source
-        
+
     def test_automatic_creation_formview(self):
         self.view('creation', None, etype='CWUser', template=None).source
-        
+
     def test_automatic_muledit_formview(self):
         rset = self.execute('CWUser X')
         self.view('muledit', rset, template=None).source
-        
+
     def test_automatic_reledit_formview(self):
         rset = self.execute('CWUser X')
         self.view('reledit', rset, row=0, rtype='login', template=None).source
-        
+
     def test_automatic_inline_edit_formview(self):
         geid = self.execute('CWGroup X LIMIT 1')[0][0]
         rset = self.execute('CWUser X LIMIT 1')
         self.view('inline-edition', rset, row=0, rtype='in_group', peid=geid, template=None).source
-                              
+
     def test_automatic_inline_creation_formview(self):
         geid = self.execute('CWGroup X LIMIT 1')[0][0]
         self.view('inline-creation', None, etype='CWUser', rtype='in_group', peid=geid, template=None).source
 
-        
+
 if __name__ == '__main__':
     unittest_main()
--- a/web/uicfg.py	Wed Apr 29 11:06:13 2009 +0200
+++ b/web/uicfg.py	Wed Apr 29 17:36:49 2009 +0200
@@ -9,32 +9,33 @@
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 __docformat__ = "restructuredtext en"
-from cubicweb.rtags import RelationTags
+
+from cubicweb.rtags import RelationTags, RelationTagsSet
 
 # autoform.AutomaticEntityForm configuration ##################################
 
 # relations'category (eg primary/secondary/generic/metadata/generated)
 rcategories = RelationTags()
 # use primary and not generated for eid since it has to be an hidden
-rcategories.set_rtag('primary', 'eid', 'subject')
-rcategories.set_rtag('primary', 'in_state', 'subject')
-rcategories.set_rtag('secondary', 'description', 'subject')
-rcategories.set_rtag('metadata', 'creation_date', 'subject')
-rcategories.set_rtag('metadata', 'modification_date', 'subject')
-rcategories.set_rtag('metadata', 'owned_by', 'subject')
-rcategories.set_rtag('metadata', 'created_by', 'subject')
-rcategories.set_rtag('generated', 'has_text', 'subject')
-rcategories.set_rtag('generated', 'is', 'subject')
-rcategories.set_rtag('generated', 'is', 'object')
-rcategories.set_rtag('generated', 'is_instance_of', 'subject')
-rcategories.set_rtag('generated', 'is_instance_of', 'object')
-rcategories.set_rtag('generated', 'identity', 'subject')
-rcategories.set_rtag('generated', 'identity', 'object')
-rcategories.set_rtag('generated', 'require_permission', 'subject')
-rcategories.set_rtag('generated', 'wf_info_for', 'subject')
-rcategories.set_rtag('generated', 'wf_info_for', 'object')
-rcategories.set_rtag('generated', 'for_user', 'subject')
-rcategories.set_rtag('generated', 'for_user', 'object')
+rcategories.tag_relation('primary', ('*', 'eid', '*'), 'subject')
+rcategories.tag_relation('primary', ('*', 'in_state', '*'), 'subject')
+rcategories.tag_relation('secondary', ('*', 'description', '*'), 'subject')
+rcategories.tag_relation('metadata', ('*', 'creation_date', '*'), 'subject')
+rcategories.tag_relation('metadata', ('*', 'modification_date', '*'), 'subject')
+rcategories.tag_relation('metadata', ('*', 'owned_by', '*'), 'subject')
+rcategories.tag_relation('metadata', ('*', 'created_by', '*'), 'subject')
+rcategories.tag_relation('generated', ('*', 'has_text', '*'), 'subject')
+rcategories.tag_relation('generated', ('*', 'is', '*'), 'subject')
+rcategories.tag_relation('generated', ('*', 'is', '*'), 'object')
+rcategories.tag_relation('generated', ('*', 'is_instance_of', '*'), 'subject')
+rcategories.tag_relation('generated', ('*', 'is_instance_of', '*'), 'object')
+rcategories.tag_relation('generated', ('*', 'identity', '*'), 'subject')
+rcategories.tag_relation('generated', ('*', 'identity', '*'), 'object')
+rcategories.tag_relation('generated', ('*', 'require_permission', '*'), 'subject')
+rcategories.tag_relation('generated', ('*', 'wf_info_for', '*'), 'subject')
+rcategories.tag_relation('generated', ('*', 'wf_info_for', '*'), 'object')
+rcategories.tag_relation('generated', ('*', 'for_user', '*'), 'subject')
+rcategories.tag_relation('generated', ('*', 'for_user', '*'), 'object')
 
 # relations'field class
 rfields = RelationTags()
@@ -50,21 +51,21 @@
 # set of tags of the form <action>_on_new on relations. <action> is a
 # schema action (add/update/delete/read), and when such a tag is found
 # permissions checking is by-passed and supposed to be ok
-rpermissions_overrides = RelationTags(use_set=True)
+rpermissions_overrides = RelationTagsSet()
 
 
 # boxes.EditBox configuration #################################################
 
 # 'link' / 'create' relation tags, used to control the "add entity" submenu
 rmode = RelationTags()
-rmode.set_rtag('link', 'is', 'subject')
-rmode.set_rtag('link', 'is', 'object')
-rmode.set_rtag('link', 'is_instance_of', 'subject')
-rmode.set_rtag('link', 'is_instance_of', 'object')
-rmode.set_rtag('link', 'identity', 'subject')
-rmode.set_rtag('link', 'identity', 'object')
-rmode.set_rtag('link', 'owned_by', 'subject')
-rmode.set_rtag('link', 'created_by', 'subject')
-rmode.set_rtag('link', 'require_permission', 'subject')
-rmode.set_rtag('link', 'wf_info_for', 'subject')
-rmode.set_rtag('link', 'wf_info_for', 'object')
+rmode.tag_relation('link', ('*', 'is', '*'), 'subject')
+rmode.tag_relation('link', ('*', 'is', '*'), 'object')
+rmode.tag_relation('link', ('*', 'is_instance_of', '*'), 'subject')
+rmode.tag_relation('link', ('*', 'is_instance_of', '*'), 'object')
+rmode.tag_relation('link', ('*', 'identity', '*'), 'subject')
+rmode.tag_relation('link', ('*', 'identity', '*'), 'object')
+rmode.tag_relation('link', ('*', 'owned_by', '*'), 'subject')
+rmode.tag_relation('link', ('*', 'created_by', '*'), 'subject')
+rmode.tag_relation('link', ('*', 'require_permission', '*'), 'subject')
+rmode.tag_relation('link', ('*', 'wf_info_for', '*'), 'subject')
+rmode.tag_relation('link', ('*', 'wf_info_for', '*'), 'object')
--- a/web/views/autoform.py	Wed Apr 29 11:06:13 2009 +0200
+++ b/web/views/autoform.py	Wed Apr 29 17:36:49 2009 +0200
@@ -13,6 +13,8 @@
 from cubicweb.web.form import FieldNotFound, EntityFieldsForm
 from cubicweb.web.formfields import guess_field
 from cubicweb.web.formwidgets import Button, SubmitButton
+from cubicweb.web.views.editforms import toggleable_relation_link, relation_id
+
 _ = unicode
 
 class AutomaticEntityForm(EntityFieldsForm):
@@ -58,7 +60,7 @@
                         X, Y = tschema, eschema
                         card = rschema.rproperty(X, Y, 'cardinality')[1]
                         composed = rschema.rproperty(X, Y, 'composite') == 'subject'
-                    if not cls.rcategories.rtag(rschema, role, X, Y):
+                    if not cls.rcategories.get(rschema, role, X, Y):
                         if card in '1+':
                             if not rschema.is_final() and composed:
                                 category = 'generated'
@@ -68,7 +70,7 @@
                             category = 'secondary'
                         else:
                             category = 'generic'
-                        cls.rcategories.set_rtag(category, rschema, role, X, Y)
+                        cls.rcategories.tag_relation(category, (X, rschema, Y), role)
 
     @classmethod
     def erelations_by_category(cls, entity, categories=None, permission=None, rtags=None):
@@ -93,14 +95,14 @@
             # permission which may imply rql queries
             if categories is not None:
                 targetschemas = [tschema for tschema in targetschemas
-                                 if rtags.etype_rtag(eschema, rschema, role, tschema) in categories]
+                                 if rtags.etype_get(eschema, rschema, role, tschema) in categories]
                 if not targetschemas:
                     continue
             if permission is not None:
                 # tag allowing to hijack the permission machinery when
                 # permission is not verifiable until the entity is actually
                 # created...
-                if eid is None and '%s_on_new' % permission in permsoverrides.etype_rtags(eschema, rschema, role):
+                if eid is None and '%s_on_new' % permission in permsoverrides.etype_get(eschema, rschema, role):
                     yield (rschema, targetschemas, role)
                     continue
                 if rschema.is_final():
@@ -159,10 +161,10 @@
             if eschema is None or not name in cls_or_self.schema:
                 raise
             rschema = cls_or_self.schema.rschema(name)
-            fieldcls = cls_or_self.rfields.etype_rtag(eschema, rschema, role)
+            fieldcls = cls_or_self.rfields.etype_get(eschema, rschema, role)
             if fieldcls:
                 return fieldcls(name=name, role=role, eidparam=True)
-            widget = cls_or_self.rwidgets.etype_rtag(eschema, rschema, role)
+            widget = cls_or_self.rwidgets.etype_get(eschema, rschema, role)
             if widget:
                 field = guess_field(eschema, rschema, role,
                                     eidparam=True, widget=widget)
@@ -241,7 +243,7 @@
     def editable_attributes(self):
         """return a list of (relation schema, role) to edit for the entity"""
         return [(rschema, x) for rschema, _, x in self.relations_by_category(
-            self.attrcategories, 'add') if rschema != 'eid']
+                self.attrcategories, 'add') if rschema != 'eid']
 
     def relations_table(self):
         """yiels 3-tuples (rtype, target, related_list)
@@ -256,9 +258,9 @@
         for label, rschema, role in self.srelations_by_category('generic', 'add'):
             relatedrset = entity.related(rschema, role, limit=self.related_limit)
             if rschema.has_perm(self.req, 'delete'):
-                toggable_rel_link_func = toggable_relation_link
+                toggleable_rel_link_func = toggleable_relation_link
             else:
-                toggable_rel_link_func = lambda x, y, z: u''
+                toggleable_rel_link_func = lambda x, y, z: u''
             related = []
             for row in xrange(relatedrset.rowcount):
                 nodeid = relation_id(entity.eid, rschema, role,
@@ -269,7 +271,7 @@
                 else:
                     status = u''
                     label = 'x'
-                dellink = toggable_rel_link_func(entity.eid, nodeid, label)
+                dellink = toggleable_rel_link_func(entity.eid, nodeid, label)
                 eview = self.view('oneline', relatedrset, row=row)
                 related.append((nodeid, dellink, status, eview))
             yield (rschema, role, related)
@@ -304,7 +306,7 @@
         """return true if the given relation with entity has role and a
         targettype target should be inlined
         """
-        return self.rinlined.etype_rtag(self.edited_entity.id, rschema, role, targettype)
+        return self.rinlined.etype_get(self.edited_entity.id, rschema, role, targettype)
 
     def should_display_inline_creation_form(self, rschema, existant, card):
         """return true if a creation form should be inlined
--- a/web/views/basecontrollers.py	Wed Apr 29 11:06:13 2009 +0200
+++ b/web/views/basecontrollers.py	Wed Apr 29 17:36:49 2009 +0200
@@ -44,6 +44,7 @@
         self.req.set_content_type('application/json')
         result = func(self, *args, **kwargs)
         return simplejson.dumps(result)
+    wrapper.__name__ = func.__name__
     return wrapper
 
 def xhtmlize(func):
@@ -52,6 +53,7 @@
         self.req.set_content_type(self.req.html_content_type())
         result = func(self, *args, **kwargs)
         return xhtml_wrap(result)
+    wrapper.__name__ = func.__name__
     return wrapper
 
 def check_pageid(func):
@@ -360,6 +362,9 @@
 
     @jsonize
     def js_validate_form(self, action, names, values):
+        return self.validate_form(action, names, values)
+
+    def validate_form(self, action, names, values):
         # XXX this method (and correspoding js calls) should use the new
         #     `RemoteCallFailed` mechansim
         self.req.form = self._rebuild_posted_form(names, values, action)
@@ -388,7 +393,7 @@
 
     @jsonize
     def js_edit_field(self, action, names, values, rtype, eid):
-        success, args = self.js_validate_form(action, names, values)
+        success, args = self.validate_form(action, names, values)
         if success:
             rset = self.req.execute('Any X,N WHERE X eid %%(x)s, X %s N' % rtype,
                                     {'x': eid}, 'x')
--- a/web/views/baseforms.py	Wed Apr 29 11:06:13 2009 +0200
+++ b/web/views/baseforms.py	Wed Apr 29 17:36:49 2009 +0200
@@ -320,8 +320,8 @@
     # should_* method extracted to allow overriding
     
     def should_inline_relation_form(self, entity, rschema, targettype, role):
-        return AutomaticEntityForm.rinlined.etype_rtag(entity.id, rschema, role,
-                                                       targettype)
+        return AutomaticEntityForm.rinlined.etype_get(entity.id, rschema, role,
+                                                      targettype)
 
     def should_display_inline_relation_form(self, rschema, existant, card):
         return not existant and card in '1+'
@@ -423,8 +423,8 @@
     def should_inline_relation_form(self, entity, rschema, targettype, role):
         if rschema == self.rschema:
             return False
-        return AutomaticEntityForm.rinlined.etype_rtag(entity.id, rschema, role,
-                                                       targettype)
+        return AutomaticEntityForm.rinlined.etype_get(entity.id, rschema, role,
+                                                      targettype)
 
     @cached
     def keep_entity(self, entity):
--- a/web/views/bookmark.py	Wed Apr 29 11:06:13 2009 +0200
+++ b/web/views/bookmark.py	Wed Apr 29 17:36:49 2009 +0200
@@ -14,8 +14,8 @@
 from cubicweb.web import uicfg, action, box, formwidgets
 from cubicweb.web.views.baseviews import PrimaryView
 
-uicfg.rcategories.set_rtag('primary', 'path', 'subject', 'Bookmark')
-uicfg.rwidgets.set_rtag(formwidgets.TextInput, 'path', 'subject', 'Bookmark')
+uicfg.rcategories.tag_relation('primary', ('Bookmark', 'path', '*'), 'subject')
+uicfg.rwidgets.tag_relation(formwidgets.TextInput, ('Bookmark', 'path', '*'), 'subject')
 
 
 class FollowAction(action.Action):
--- a/web/views/boxes.py	Wed Apr 29 11:06:13 2009 +0200
+++ b/web/views/boxes.py	Wed Apr 29 17:36:49 2009 +0200
@@ -53,7 +53,7 @@
                     else:
                         X, Y = tschema, eschema
                         card = rschema.rproperty(X, Y, 'cardinality')[1]
-                    if not cls.rmode.rtag(rschema, role, X, Y):
+                    if not cls.rmode.get(rschema, role, X, Y):
                         if card in '?1':
                             # by default, suppose link mode if cardinality doesn't allow
                             # more than one relation
@@ -64,7 +64,7 @@
                         else:
                             # link mode by default
                             mode = 'link'
-                        cls.rmode.set_rtag(mode, rschema, role, X, Y)
+                        cls.rmode.tag_relation(mode, (X, rschema, Y), role)
 
     @classmethod
     def relation_mode(cls, rtype, etype, targettype, role='subject'):
@@ -72,8 +72,8 @@
         to a new entity ('create' mode) or to an existant entity ('link' mode)
         """
         if role == 'subject':
-            return cls.rmode.rtag(rtype, role, etype, targettype)
-        return cls.rmode.rtag(rtype, role, targettype, etype)
+            return cls.rmode.get(rtype, role, etype, targettype)
+        return cls.rmode.get(rtype, role, targettype, etype)
 
 
     def call(self, view=None, **kwargs):
--- a/web/views/cwproperties.py	Wed Apr 29 11:06:13 2009 +0200
+++ b/web/views/cwproperties.py	Wed Apr 29 17:36:49 2009 +0200
@@ -12,10 +12,9 @@
 
 from cubicweb import UnknownProperty
 from cubicweb.selectors import (one_line_rset, none_rset, implements,
-                                match_user_groups, entity_implements)
-from cubicweb.utils import UStringIO
+                                match_user_groups)
 from cubicweb.view import StartupView
-from cubicweb.web import INTERNAL_FIELD_VALUE, eid_param, uicfg
+from cubicweb.web import uicfg
 from cubicweb.web.views import baseviews
 from cubicweb.web import stdmsgs
 from cubicweb.web.form import CompositeForm, EntityFieldsForm, FormViewMixIn
@@ -324,6 +323,6 @@
                 wdg.attrs.setdefault('size', 3)
         self.widget = wdg
 
-uicfg.rfields.set_rtag(PropertyKeyField, 'pkey', 'subject', 'CWProperty')
-uicfg.rfields.set_rtag(PropertyValueField, 'value', 'subject', 'CWProperty')
 
+uicfg.rfields.tag_relation(PropertyKeyField, ('CWProperty', 'pkey', '*'), 'subject')
+uicfg.rfields.tag_relation(PropertyValueField, ('CWProperty', 'value', '*'), 'subject')
--- a/web/views/cwuser.py	Wed Apr 29 11:06:13 2009 +0200
+++ b/web/views/cwuser.py	Wed Apr 29 17:36:49 2009 +0200
@@ -14,18 +14,18 @@
 from cubicweb.web.views.baseviews import PrimaryView
 
 
-uicfg.rcategories.set_rtag('secondary', 'firstname', 'subject', 'CWUser')
-uicfg.rcategories.set_rtag('secondary', 'surname', 'subject', 'CWUser')
-uicfg.rcategories.set_rtag('metadata', 'last_login_time', 'subject', 'CWUser')
-uicfg.rcategories.set_rtag('primary', 'in_group', 'subject', 'CWUser')
-uicfg.rcategories.set_rtag('generated', 'owned_by', 'object', otype='CWUser')
-uicfg.rcategories.set_rtag('generated', 'created_by', 'object', otype='CWUser')
-uicfg.rcategories.set_rtag('metadata', 'bookmarked_by', 'object', otype='CWUser')
-uicfg.rinlined.set_rtag(True, 'use_email', 'subject', 'CWUser')
-uicfg.rmode.set_rtag('create', 'in_group', 'object', otype='CWGroup')
-uicfg.rmode.set_rtag('link', 'owned_by', 'object', otype='CWUser')
-uicfg.rmode.set_rtag('link', 'created_by', 'object', otype='CWUser')
-uicfg.rmode.set_rtag('create', 'bookmarked_by', 'object', otype='CWUser')
+uicfg.rcategories.tag_relation('secondary', ('CWUser', 'firstname', '*'), 'subject')
+uicfg.rcategories.tag_relation('secondary', ('CWUser', 'surname', '*'), 'subject')
+uicfg.rcategories.tag_relation('metadata', ('CWUser', 'last_login_time', '*'), 'subject')
+uicfg.rcategories.tag_relation('primary', ('CWUser', 'in_group', '*'), 'subject')
+uicfg.rcategories.tag_relation('generated', ('*', 'owned_by', 'CWUser'), 'object')
+uicfg.rcategories.tag_relation('generated', ('*', 'created_by', 'CWUser'), 'object')
+uicfg.rcategories.tag_relation('metadata', ('*', 'bookmarked_by', 'CWUser'), 'object')
+uicfg.rinlined.tag_relation(True, ('CWUser', 'use_email', '*'), 'subject')
+uicfg.rmode.tag_relation('create', ('*', 'in_group', 'CWGroup'), 'object')
+uicfg.rmode.tag_relation('link', ('*', 'owned_by', 'CWUser'), 'object')
+uicfg.rmode.tag_relation('link', ('*', 'created_by', 'CWUser'), 'object')
+uicfg.rmode.tag_relation('create', ('*', 'bookmarked_by', 'CWUser'), 'object')
 
 
 class UserPreferencesEntityAction(action.Action):
--- a/web/views/editforms.py	Wed Apr 29 11:06:13 2009 +0200
+++ b/web/views/editforms.py	Wed Apr 29 17:36:49 2009 +0200
@@ -33,7 +33,7 @@
         return u'%s:%s:%s' % (eid, rtype, reid)
     return u'%s:%s:%s' % (reid, rtype, eid)
 
-def toggable_relation_link(eid, nodeid, label='x'):
+def toggleable_relation_link(eid, nodeid, label='x'):
     """return javascript snippet to delete/undelete a relation between two
     entities
     """
--- a/web/views/editviews.py	Wed Apr 29 11:06:13 2009 +0200
+++ b/web/views/editviews.py	Wed Apr 29 17:36:49 2009 +0200
@@ -120,7 +120,7 @@
         pending_inserts = self.req.get_pending_inserts(eid)
         rtype = rschema.type
         form = self.vreg.select_object('forms', 'edition', self.req,
-                                       entity=entity)
+                                       self.rset, entity=entity)
         field = form.field_by_name(rschema, target, entity.e_schema)
         limit = self.req.property_value('navigation.combobox-limit')
         for eview, reid in form.form_field_vocabulary(field, limit):
--- a/web/views/management.py	Wed Apr 29 11:06:13 2009 +0200
+++ b/web/views/management.py	Wed Apr 29 17:36:49 2009 +0200
@@ -62,6 +62,7 @@
                 return True
         return False
 
+
 class SecurityManagementView(EntityView, SecurityViewMixIn):
     """display security information for a given entity"""
     id = 'security'
@@ -100,6 +101,7 @@
         msg = self.req._('ownerships have been changed')
         form = EntityFieldsForm(self.req, None, entity=entity, submitmsg=msg,
                                 form_buttons=[formwidgets.SubmitButton()],
+                                domid='ownership%s' % entity.eid,
                                 __redirectvid='security',
                                 __redirectpath=entity.rest_path())
         field = guess_field(entity.e_schema, self.schema.rschema('owned_by'))
@@ -156,14 +158,17 @@
         w(u'<p>%s</p>' % _('add a new permission'))
         form = EntityFieldsForm(self.req, None, entity=newperm,
                                 form_buttons=[formwidgets.SubmitButton()],
+                                domid='reqperm%s' % entity.eid,
                                 __redirectvid='security',
                                 __redirectpath=entity.rest_path())
-        form.form_add_hidden('require_permission', entity.eid, role='object', eidparam=True)
+        form.form_add_hidden('require_permission', entity.eid, role='object',
+                             eidparam=True)
         permnames = getattr(entity, '__permissions__', None)
         cwpermschema = newperm.e_schema
         if permnames is not None:
             field = guess_field(cwpermschema, self.schema.rschema('name'),
-                                widget=formwidgets.Select, choices=permnames)
+                                widget=formwidgets.Select({'size': 1}),
+                                choices=permnames)
         else:
             field = guess_field(cwpermschema, self.schema.rschema('name'))
         form.append_field(field)
--- a/web/views/schema.py	Wed Apr 29 11:06:13 2009 +0200
+++ b/web/views/schema.py	Wed Apr 29 17:36:49 2009 +0200
@@ -19,19 +19,19 @@
 from cubicweb.web.views import TmpFileViewMixin, baseviews
 
 
-uicfg.rcategories.set_rtag('primary', 'require_group', 'subject', 'CWPermission')
-uicfg.rcategories.set_rtag('generated', 'final', 'subject', 'EEtype')
-uicfg.rcategories.set_rtag('generated', 'final', 'subject', 'ERtype')
-uicfg.rinlined.set_rtag(True, 'relation_type', 'subject', 'CWRelation')
-uicfg.rinlined.set_rtag(True, 'from_entity', 'subject', 'CWRelation')
-uicfg.rinlined.set_rtag(True, 'to_entity', 'subject', 'CWRelation')
-uicfg.rwidgets.set_rtag('StringWidget', 'expression', 'subject', 'RQLExpression')
+uicfg.rcategories.tag_relation('primary', ('CWPermission', 'require_group', '*'), 'subject')
+uicfg.rcategories.tag_relation('generated', ('EEtype', 'final', '*'), 'subject')
+uicfg.rcategories.tag_relation('generated', ('ERtype', 'final', '*'), 'subject')
+uicfg.rinlined.tag_relation(True, ('CWRelation', 'relation_type', '*'), 'subject')
+uicfg.rinlined.tag_relation(True, ('CWRelation', 'from_entity', '*'), 'subject')
+uicfg.rinlined.tag_relation(True, ('CWRelation', 'to_entity', '*'), 'subject')
+uicfg.rwidgets.tag_relation('StringWidget', ('RQLExpression', 'expression', '*'), 'subject')
 
-uicfg.rmode.set_rtag('create', 'state_of', 'object', otype='CWEType')
-uicfg.rmode.set_rtag('create', 'transition_of', 'object', otype='CWEType')
-uicfg.rmode.set_rtag('create', 'relation_type', 'object', otype='CWRType')
-uicfg.rmode.set_rtag('link', 'from_entity', 'object', otype='CWEType')
-uicfg.rmode.set_rtag('link', 'to_entity', 'object', otype='CWEType')
+uicfg.rmode.tag_relation('create', ('*', 'state_of', 'CWEType'), 'object')
+uicfg.rmode.tag_relation('create', ('*', 'transition_of', 'CWEType'), 'object')
+uicfg.rmode.tag_relation('create', ('*', 'relation_type', 'CWRType'), 'object')
+uicfg.rmode.tag_relation('link', ('*', 'from_entity', 'CWEType'), 'object')
+uicfg.rmode.tag_relation('link', ('*', 'to_entity', 'CWEType'), 'object')
 
 
 class ViewSchemaAction(action.Action):
--- a/web/views/startup.py	Wed Apr 29 11:06:13 2009 +0200
+++ b/web/views/startup.py	Wed Apr 29 17:36:49 2009 +0200
@@ -189,10 +189,10 @@
             self.wview(section, None)
         self.w(u'</div>')
 
-    
+
 class ManagerSchemaPermissionsView(StartupView, SecurityViewMixIn):
     id = 'schema_security'
-    __selectors__ = StartupView.__selectors__ + (match_user_groups('managers'),)
+    __select__ = StartupView.__select__ & match_user_groups('managers')
 
     def call(self, display_relations=True,
              skiprels=('is', 'is_instance_of', 'identity', 'owned_by', 'created_by')):
@@ -208,7 +208,7 @@
             entities = [eschema for eschema in entities
                         if not eschema.meta]
         # compute relations
-        relations = []    
+        relations = []
         if display_relations:
             relations = [rschema for rschema in schema.relations()
                          if not (rschema.is_final() or rschema.type in skiprels)]
@@ -288,7 +288,7 @@
             self.schema_definition(rschema, link=False)
             self.w(u'</div>')
 
-                
+
 class SchemaUreportsView(StartupView):
     id = 'schematext'
 
--- a/web/views/workflow.py	Wed Apr 29 11:06:13 2009 +0200
+++ b/web/views/workflow.py	Wed Apr 29 17:36:49 2009 +0200
@@ -25,10 +25,10 @@
 
 _ = unicode
 
-EditBox.rmode.set_rtag('create', 'destination_state', 'subject', 'Transition')
-EditBox.rmode.set_rtag('create', 'allowed_transition', 'object', otype='Transition')
-EditBox.rmode.set_rtag('create', 'destination_state', 'object', otype='State')
-EditBox.rmode.set_rtag('create', 'allowed_transition', 'subject', 'State')
+EditBox.rmode.tag_relation('create', ('Transition', 'destination_state', '*'), 'subject')
+EditBox.rmode.tag_relation('create', ('*', 'allowed_transition', 'Transition'), 'object')
+EditBox.rmode.tag_relation('create', ('*', 'destination_state', 'State'), 'object')
+EditBox.rmode.tag_relation('create', ('State', 'allowed_transition', '*'), 'subject')
 
 
 # IWorkflowable views #########################################################