merge
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Wed, 31 Mar 2010 15:39:09 +0200
changeset 5121 a63d7886fcf5
parent 5082 d6fd82a5a4e8 (current diff)
parent 5120 3c58429f8b5c (diff)
child 5125 eaec839ad3fe
merge
__pkginfo__.py
cwconfig.py
cwctl.py
devtools/testlib.py
--- a/.hgtags	Tue Mar 30 14:32:03 2010 +0200
+++ b/.hgtags	Wed Mar 31 15:39:09 2010 +0200
@@ -115,3 +115,5 @@
 f9fce56d6a0c2bc6c4b497b66039a8bbbbdc8074 cubicweb-debian-version-3.6.3-1
 d010f749c21d55cd85c5feb442b9cf816282953c cubicweb-version-3.7.2
 8fda29a6c2191ba3cc59242c17b28b34127c75fa cubicweb-debian-version-3.7.2-1
+768beb8e15f15e079f8ee6cfc35125e12b19e140 cubicweb-version-3.7.3
+44c7bf90df71dd562e5a7be5ced3019da603d24f cubicweb-debian-version-3.7.3-1
--- a/MANIFEST.in	Tue Mar 30 14:32:03 2010 +0200
+++ b/MANIFEST.in	Wed Mar 31 15:39:09 2010 +0200
@@ -15,7 +15,7 @@
 recursive-include etwist *.xml *.html
 
 recursive-include i18n *.pot *.po
-recursive-include schemas *.py *.sql.*
+recursive-include schemas *.py *.sql
 
 recursive-include entities/test/data *
 recursive-include sobjects/test/data *
--- a/__pkginfo__.py	Tue Mar 30 14:32:03 2010 +0200
+++ b/__pkginfo__.py	Wed Mar 31 15:39:09 2010 +0200
@@ -7,7 +7,7 @@
 
 modname = distname = "cubicweb"
 
-numversion = (3, 7, 2)
+numversion = (3, 7, 3)
 version = '.'.join(str(num) for num in numversion)
 
 description = "a repository of entities / relations for knowledge management"
@@ -95,8 +95,9 @@
          [join(_data_dir, fname) for fname in listdir(_data_dir)
           if not isdir(join(_data_dir, fname))]],
         [join('share', 'cubicweb', 'cubes', 'shared', 'data', 'timeline'),
-         [join(_data_dir, 'timeline', fname)
-          for fname in listdir(join(_data_dir, 'timeline'))]],
+         [join(data_dir, 'timeline', fname) for fname in listdir(join(data_dir, 'timeline'))]],
+        [join('share', 'cubicweb', 'cubes', 'shared', 'data', 'images'),
+         [join(data_dir, 'images', fname) for fname in listdir(join(data_dir, 'images'))]],
         [join('share', 'cubicweb', 'cubes', 'shared', 'wdoc'),
          [join(_wdoc_dir, fname) for fname in listdir(_wdoc_dir)
           if not isdir(join(_wdoc_dir, fname))]],
--- a/appobject.py	Tue Mar 30 14:32:03 2010 +0200
+++ b/appobject.py	Wed Mar 31 15:39:09 2010 +0200
@@ -14,6 +14,7 @@
 from warnings import warn
 
 from logilab.common.deprecation import deprecated
+from logilab.common.decorators import classproperty
 from logilab.common.logging_ext import set_log_methods
 
 
@@ -245,6 +246,12 @@
     __regid__ = None
     __select__ = yes()
 
+    @classproperty
+    def __registries__(cls):
+        if cls.__registry__ is None:
+            return ()
+        return (cls.__registry__,)
+
     @classmethod
     def __registered__(cls, registry):
         """called by the registry when the appobject has been registered.
--- a/cwconfig.py	Tue Mar 30 14:32:03 2010 +0200
+++ b/cwconfig.py	Wed Mar 31 15:39:09 2010 +0200
@@ -337,7 +337,7 @@
     def available_cubes(cls):
         cubes = set()
         for directory in cls.cubes_search_path():
-            if not os.path.exists(directory):
+            if not exists(directory):
                 cls.error('unexistant directory in cubes search path: %s'
                            % directory)
                 continue
--- a/cwctl.py	Tue Mar 30 14:32:03 2010 +0200
+++ b/cwctl.py	Wed Mar 31 15:39:09 2010 +0200
@@ -725,7 +725,7 @@
         # handle i18n upgrade:
         # * install new languages
         # * recompile catalogs
-        # in the first componant given
+        # XXX search available language in the first cube given
         from cubicweb import i18n
         templdir = cwcfg.cube_dir(config.cubes()[0])
         langs = [lang for lang, _ in i18n.available_catalogs(join(templdir, 'i18n'))]
@@ -740,7 +740,12 @@
         print
         print '-> instance migrated.'
         if not (CWDEV or self.config.nostartstop):
-            StartInstanceCommand().start_instance(appid)
+            # restart instance through fork to get a proper environment, avoid
+            # uicfg pb (and probably gettext catalogs, to check...)
+            forkcmd = '%s start %s' % (sys.argv[0], appid)
+            status = system(forkcmd)
+            if status:
+                print '%s exited with status %s' % (forkcmd, status)
         print
 
 
--- a/dataimport.py	Tue Mar 30 14:32:03 2010 +0200
+++ b/dataimport.py	Wed Mar 31 15:39:09 2010 +0200
@@ -498,7 +498,7 @@
                     err = func(buckets)
                     if err:
                         self.errors[title] = (help, err)
-        self.store.commit()
+        txuuid = self.store.commit()
         self._print_stats()
         if self.errors:
             if self.askerror == 2 or (self.askerror and confirm('Display errors ?')):
@@ -506,7 +506,8 @@
                 for errkey, error in self.errors.items():
                     self.tell("\n%s (%s): %d\n" % (error[0], errkey, len(error[1])))
                     self.tell(pformat(sorted(error[1])))
-
+        if txuuid is not None:
+            print 'transaction id:', txuuid
     def _print_stats(self):
         nberrors = sum(len(err[1]) for err in self.errors.values())
         self.tell('\nImport statistics: %i entities, %i types, %i relations and %i errors'
--- a/dbapi.py	Tue Mar 30 14:32:03 2010 +0200
+++ b/dbapi.py	Wed Mar 31 15:39:09 2010 +0200
@@ -367,6 +367,16 @@
             return '<Connection %s (anonymous)>' % self.sessionid
         return '<Connection %s>' % self.sessionid
 
+    def __enter__(self):
+        return self.cursor()
+
+    def __exit__(self, exc_type, exc_val, exc_tb):
+        if exc_type is None:
+            self.commit()
+        else:
+            self.rollback()
+            return False #propagate the exception
+
     def request(self):
         return DBAPIRequest(self.vreg, self)
 
--- a/debian/changelog	Tue Mar 30 14:32:03 2010 +0200
+++ b/debian/changelog	Wed Mar 31 15:39:09 2010 +0200
@@ -1,3 +1,9 @@
+cubicweb (3.7.3-1) unstable; urgency=low
+
+  * new upstream release
+
+ -- Sylvain Thénault <sylvain.thenault@logilab.fr>  Wed, 31 Mar 2010 14:55:21 +0200
+
 cubicweb (3.7.2-1) unstable; urgency=low
 
   * new upstream release
--- a/devtools/testlib.py	Tue Mar 30 14:32:03 2010 +0200
+++ b/devtools/testlib.py	Wed Mar 31 15:39:09 2010 +0200
@@ -14,6 +14,7 @@
 import re
 from urllib import unquote
 from math import log
+from contextlib import contextmanager
 
 import simplejson
 
@@ -357,6 +358,15 @@
     def entity(self, rql, args=None, eidkey=None, req=None):
         return self.execute(rql, args, eidkey, req=req).get_entity(0, 0)
 
+    @contextmanager
+    def temporary_appobjects(self, *appobjects):
+        self.vreg._loadedmods.setdefault(self.__module__, {})
+        for obj in appobjects:
+            self.vreg.register(obj)
+        yield
+        for obj in appobjects:
+            self.vreg.unregister(obj)
+
     # vregistry inspection utilities ###########################################
 
     def pviews(self, req, rset):
--- a/entity.py	Tue Mar 30 14:32:03 2010 +0200
+++ b/entity.py	Wed Mar 31 15:39:09 2010 +0200
@@ -298,6 +298,12 @@
             self.edited_attributes.remove(attr)
         return value
 
+    def update(self, values):
+        """override update to update self.edited_attributes. See `__setitem__`
+        """
+        for attr, value in values.items():
+            self[attr] = value # use self.__setitem__ implementation
+
     def rql_set_value(self, attr, value):
         """call by rql execution plan when some attribute is modified
 
@@ -874,17 +880,21 @@
     # raw edition utilities ###################################################
 
     def set_attributes(self, **kwargs):
+        _check_cw_unsafe(kwargs)
         assert kwargs
-        _check_cw_unsafe(kwargs)
+        assert self._is_saved
         relations = []
         for key in kwargs:
             relations.append('X %s %%(%s)s' % (key, key))
-        # update current local object
-        self.update(kwargs)
         # and now update the database
         kwargs['x'] = self.eid
         self._cw.execute('SET %s WHERE X eid %%(x)s' % ','.join(relations),
                          kwargs, 'x')
+        kwargs.pop('x')
+        # update current local object _after_ the rql query to avoid
+        # interferences between the query execution itself and the
+        # edited_attributes / skip_security_attributes machinery
+        self.update(kwargs)
 
     def set_relations(self, **kwargs):
         """add relations to the given object. To set a relation where this entity
--- a/etwist/service.py	Tue Mar 30 14:32:03 2010 +0200
+++ b/etwist/service.py	Wed Mar 31 15:39:09 2010 +0200
@@ -17,7 +17,6 @@
 os.environ['CW_INSTANCES_DIR'] = r'C:\etc\cubicweb.d'
 os.environ['USERNAME'] = 'cubicweb'
 
-
 class CWService(object, win32serviceutil.ServiceFramework):
     _svc_name_ = None
     _svc_display_name_ = None
@@ -25,7 +24,6 @@
 
     def __init__(self, *args, **kwargs):
         win32serviceutil.ServiceFramework.__init__(self, *args, **kwargs)
-        self._stop_event = win32event.CreateEvent(None, 0, 0, None)
         cwcfg.load_cwctl_plugins()
         set_log_methods(CubicWebRootResource, logger)
         server.parsePOSTData = parsePOSTData
@@ -33,8 +31,8 @@
     def SvcStop(self):
         self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
         logger.info('stopping %s service' % self.instance)
-        win32event.SetEvent(self._stop_event)
-        self.ReportServiceStatus(win32service.SERVICE_STOPPED)
+        reactor.stop()
+        self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
 
     def SvcDoRun(self):
         self.ReportServiceStatus(win32service.SERVICE_START_PENDING)
@@ -57,5 +55,4 @@
         except Exception, e:
             logger.error('service %s stopped (cause: %s)' % (self.instance, e))
             logger.exception('what happened ...')
-            self.SvcStop()
-
+        self.ReportServiceStatus(win32service.SERVICE_STOPPED)
--- a/hooks/__init__.py	Tue Mar 30 14:32:03 2010 +0200
+++ b/hooks/__init__.py	Wed Mar 31 15:39:09 2010 +0200
@@ -24,7 +24,7 @@
             session = repo.internal_session()
             try:
                 session.system_sql(
-                    'DELETE FROM transaction WHERE tx_time < %(time)s',
+                    'DELETE FROM transactions WHERE tx_time < %(time)s',
                     {'time': mindate})
                 # cleanup deleted entities
                 session.system_sql(
--- a/i18n/en.po	Tue Mar 30 14:32:03 2010 +0200
+++ b/i18n/en.po	Wed Mar 31 15:39:09 2010 +0200
@@ -332,6 +332,18 @@
 "anymore."
 msgstr ""
 
+#, python-format
+msgid ""
+"Can't undo addition of relation %(rtype)s from %(subj)s to %(obj)s, doesn't "
+"exist anymore"
+msgstr ""
+
+#, python-format
+msgid ""
+"Can't undo creation of entity %(eid)s of type %(etype)s, type no more "
+"supported"
+msgstr ""
+
 msgid "Date"
 msgstr "Date"
 
@@ -3358,6 +3370,9 @@
 msgid "some errors occured:"
 msgstr ""
 
+msgid "some later transaction(s) touch entity, undo them first"
+msgstr ""
+
 msgid "sorry, the server is unable to handle this query"
 msgstr ""
 
--- a/i18n/es.po	Tue Mar 30 14:32:03 2010 +0200
+++ b/i18n/es.po	Wed Mar 31 15:39:09 2010 +0200
@@ -340,6 +340,18 @@
 "anymore."
 msgstr ""
 
+#, python-format
+msgid ""
+"Can't undo addition of relation %(rtype)s from %(subj)s to %(obj)s, doesn't "
+"exist anymore"
+msgstr ""
+
+#, python-format
+msgid ""
+"Can't undo creation of entity %(eid)s of type %(etype)s, type no more "
+"supported"
+msgstr ""
+
 msgid "Date"
 msgstr "Fecha"
 
@@ -3439,6 +3451,9 @@
 msgid "some errors occured:"
 msgstr ""
 
+msgid "some later transaction(s) touch entity, undo them first"
+msgstr ""
+
 msgid "sorry, the server is unable to handle this query"
 msgstr "lo sentimos, el servidor no puede manejar esta consulta"
 
--- a/i18n/fr.po	Tue Mar 30 14:32:03 2010 +0200
+++ b/i18n/fr.po	Wed Mar 31 15:39:09 2010 +0200
@@ -347,6 +347,22 @@
 "Ne peut restaurer la relation %(rtype)s, l'entité %(role)s %(eid)s n'existe "
 "plus."
 
+#, python-format
+msgid ""
+"Can't undo addition of relation %(rtype)s from %(subj)s to %(obj)s, doesn't "
+"exist anymore"
+msgstr ""
+"Ne peut annuler l'ajout de relation %(rtype)s de %(subj)s vers %(obj)s, "
+"cette relation n'existe plus"
+
+#, python-format
+msgid ""
+"Can't undo creation of entity %(eid)s of type %(etype)s, type no more "
+"supported"
+msgstr ""
+"Ne peut annuler la création de l'entité %(eid)s de type %(etype)s, ce type "
+"n'existe plus"
+
 msgid "Date"
 msgstr "Date"
 
@@ -3471,6 +3487,10 @@
 msgid "some errors occured:"
 msgstr "des erreurs sont survenues"
 
+msgid "some later transaction(s) touch entity, undo them first"
+msgstr ""
+"des transactions plus récentes modifient cette entité, annulez les d'abord"
+
 msgid "sorry, the server is unable to handle this query"
 msgstr "désolé, le serveur ne peut traiter cette requête"
 
--- a/selectors.py	Tue Mar 30 14:32:03 2010 +0200
+++ b/selectors.py	Wed Mar 31 15:39:09 2010 +0200
@@ -58,26 +58,29 @@
 
 # helpers for debugging selectors
 SELECTOR_LOGGER = logging.getLogger('cubicweb.selectors')
-TRACED_OIDS = ()
+TRACED_OIDS = None
+
+def _trace_selector(cls, ret):
+    # /!\ lltrace decorates pure function or __call__ method, this
+    #     means argument order may be different
+    if isinstance(cls, Selector):
+        selname = str(cls)
+        vobj = args[0]
+    else:
+        selname = selector.__name__
+        vobj = cls
+    if TRACED_OIDS == 'all' or class_regid(vobj) in TRACED_OIDS:
+        #SELECTOR_LOGGER.warning('selector %s returned %s for %s', selname, ret, cls)
+        print '%s -> %s for %s(%s)' % (selname, ret, vobj, vobj.__regid__)
 
 def lltrace(selector):
     # don't wrap selectors if not in development mode
     if CubicWebConfiguration.mode == 'system': # XXX config.debug
         return selector
     def traced(cls, *args, **kwargs):
-        # /!\ lltrace decorates pure function or __call__ method, this
-        #     means argument order may be different
-        if isinstance(cls, Selector):
-            selname = str(cls)
-            vobj = args[0]
-        else:
-            selname = selector.__name__
-            vobj = cls
-        oid = class_regid(vobj)
         ret = selector(cls, *args, **kwargs)
-        if TRACED_OIDS == 'all' or oid in TRACED_OIDS:
-            #SELECTOR_LOGGER.warning('selector %s returned %s for %s', selname, ret, cls)
-            print '%s -> %s for %s(%s)' % (selname, ret, vobj, vobj.__regid__)
+        if TRACED_OIDS is not None:
+            _trace_selector(cls, ret)
         return ret
     traced.__name__ = selector.__name__
     traced.__doc__ = selector.__doc__
@@ -108,7 +111,7 @@
 
     def __exit__(self, exctype, exc, traceback):
         global TRACED_OIDS
-        TRACED_OIDS = ()
+        TRACED_OIDS = None
         return traceback is None
 
 
--- a/server/__init__.py	Tue Mar 30 14:32:03 2010 +0200
+++ b/server/__init__.py	Wed Mar 31 15:39:09 2010 +0200
@@ -210,8 +210,11 @@
     paths = [p for p in config.cubes_path() + [config.apphome]
              if exists(join(p, 'migration'))]
     # deactivate every hooks but those responsible to set metadata
-    # so, NO INTEGRITY CHECKS are done, to have quicker db creation
-    with hooks_control(session, session.HOOKS_DENY_ALL, 'metadata'):
+    # so, NO INTEGRITY CHECKS are done, to have quicker db creation.
+    # Active integrity is kept else we may pb such as two default
+    # workflows for one entity type.
+    with hooks_control(session, session.HOOKS_DENY_ALL, 'metadata',
+                       'activeintegrity'):
         # execute cubicweb's pre<event> script
         mhandler.exec_event_script('pre%s' % event)
         # execute cubes pre<event> script if any
--- a/server/hook.py	Tue Mar 30 14:32:03 2010 +0200
+++ b/server/hook.py	Wed Mar 31 15:39:09 2010 +0200
@@ -48,6 +48,7 @@
 from logilab.common.deprecation import deprecated
 from logilab.common.logging_ext import set_log_methods
 
+from cubicweb import RegistryNotFound
 from cubicweb.cwvreg import CWRegistry, VRegistry
 from cubicweb.selectors import (objectify_selector, lltrace, ExpectedValueSelector,
                                 implements)
@@ -66,15 +67,14 @@
 
 
 class HooksRegistry(CWRegistry):
+    def initialization_completed(self):
+        for appobjects in self.values():
+            for cls in appobjects:
+                if not cls.enabled:
+                    warn('[3.6] %s: enabled is deprecated' % cls)
+                    self.unregister(cls)
 
     def register(self, obj, **kwargs):
-        try:
-            iter(obj.events)
-        except AttributeError:
-            raise
-        except:
-            raise Exception('bad .events attribute %s on %s.%s' % (
-                obj.events, obj.__module__, obj.__name__))
         for event in obj.events:
             if event not in ALL_HOOKS:
                 raise Exception('bad event %s on %s.%s' % (
@@ -96,7 +96,19 @@
                     for hook in hooks:
                         hook()
 
-VRegistry.REGISTRY_FACTORY['hooks'] = HooksRegistry
+class HooksManager(object):
+    def __init__(self, vreg):
+        self.vreg = vreg
+
+    def call_hooks(self, event, session=None, **kwargs):
+        try:
+            self.vreg['%s_hooks' % event].call_hooks(event, session, **kwargs)
+        except RegistryNotFound:
+            pass # no hooks for this event
+
+
+for event in ALL_HOOKS:
+    VRegistry.REGISTRY_FACTORY['%s_hooks' % event] = HooksRegistry
 
 _MARKER = object()
 def entity_oldnewvalue(entity, attr):
@@ -116,21 +128,6 @@
 
 @objectify_selector
 @lltrace
-def _bw_is_enabled(cls, req, **kwargs):
-    if cls.enabled:
-        return 1
-    warn('[3.6] %s: enabled is deprecated' % cls)
-    return 0
-
-@objectify_selector
-@lltrace
-def match_event(cls, req, **kwargs):
-    if kwargs.get('event') in cls.events:
-        return 1
-    return 0
-
-@objectify_selector
-@lltrace
 def enabled_category(cls, req, **kwargs):
     if req is None:
         return True # XXX how to deactivate server startup / shutdown event
@@ -190,11 +187,11 @@
                 return 1
         return 0
 
+
 # base class for hook ##########################################################
 
 class Hook(AppObject):
-    __registry__ = 'hooks'
-    __select__ = match_event() & enabled_category() & _bw_is_enabled()
+    __select__ = enabled_category()
     # set this in derivated classes
     events = None
     category = None
@@ -203,6 +200,16 @@
     enabled = True
 
     @classproperty
+    def __registries__(cls):
+        try:
+            return ['%s_hooks' % ev for ev in cls.events]
+        except AttributeError:
+            raise
+        except TypeError:
+            raise Exception('bad .events attribute %s on %s.%s' % (
+                cls.events, cls.__module__, cls.__name__))
+
+    @classproperty
     def __regid__(cls):
         warn('[3.6] %s.%s: please specify an id for your hook'
              % (cls.__module__, cls.__name__), DeprecationWarning)
--- a/server/repository.py	Tue Mar 30 14:32:03 2010 +0200
+++ b/server/repository.py	Wed Mar 31 15:39:09 2010 +0200
@@ -24,7 +24,6 @@
 from os.path import join
 from datetime import datetime
 from time import time, localtime, strftime
-#from pickle import dumps
 
 from logilab.common.decorators import cached
 from logilab.common.compat import any
@@ -38,7 +37,7 @@
                       UnknownEid, AuthenticationError, ExecutionError,
                       ETypeNotSupportedBySources, MultiSourcesError,
                       BadConnectionId, Unauthorized, ValidationError,
-                      typed_eid, onevent)
+                      RepositoryError, typed_eid, onevent)
 from cubicweb import cwvreg, schema, server
 from cubicweb.server import utils, hook, pool, querier, sources
 from cubicweb.server.session import Session, InternalSession, InternalManager, \
@@ -59,7 +58,7 @@
     # XXX we should imo rely on the orm to first fetch existing entity if any
     # then delete it.
     if session.is_internal_session \
-           or not session.is_hook_category_activated('integrity'):
+           or not session.is_hook_category_activated('activeintegrity'):
         return
     card = session.schema_rproperty(rtype, eidfrom, eidto, 'cardinality')
     # one may be tented to check for neweids but this may cause more than one
@@ -153,10 +152,14 @@
         self._available_pools.put_nowait(pool.ConnectionsPool(self.sources))
         if config.quick_start:
             # quick start, usually only to get a minimal repository to get cubes
-            # information (eg dump/restore/
+            # information (eg dump/restore/...)
             config._cubes = ()
-            self.set_schema(config.load_schema(), resetvreg=False)
+            # only load hooks and entity classes in the registry
+            config.cube_appobject_path = set(('hooks', 'entities'))
+            config.cubicweb_appobject_path = set(('hooks', 'entities'))
+            self.set_schema(config.load_schema())
             config['connections-pool-size'] = 1
+            # will be reinitialized later from cubes found in the database
             config._cubes = None
         elif config.creating:
             # repository creation
@@ -202,8 +205,7 @@
         self._shutting_down = False
         if config.quick_start:
             config.init_cubes(self.get_cubes())
-        else:
-            self.hm = self.vreg['hooks']
+        self.hm = hook.HooksManager(self.vreg)
 
     # internals ###############################################################
 
@@ -1064,62 +1066,68 @@
         if server.DEBUG & server.DBG_REPO:
             print 'UPDATE entity', entity.__regid__, entity.eid, \
                   dict(entity), edited_attributes
-        entity.edited_attributes = edited_attributes
-        if session.is_hook_category_activated('integrity'):
-            entity.check()
+        hm = self.hm
         eschema = entity.e_schema
         session.set_entity_cache(entity)
-        only_inline_rels, need_fti_update = True, False
-        relations = []
-        for attr in edited_attributes:
-            if attr == 'eid':
-                continue
-            rschema = eschema.subjrels[attr]
-            if rschema.final:
-                if getattr(eschema.rdef(attr), 'fulltextindexed', False):
-                    need_fti_update = True
-                only_inline_rels = False
-            else:
-                # inlined relation
-                previous_value = entity.related(attr) or None
-                if previous_value is not None:
-                    previous_value = previous_value[0][0] # got a result set
-                    if previous_value == entity[attr]:
-                        previous_value = None
+        orig_edited_attributes = getattr(entity, 'edited_attributes', None)
+        entity.edited_attributes = edited_attributes
+        try:
+            if session.is_hook_category_activated('integrity'):
+                entity.check()
+            only_inline_rels, need_fti_update = True, False
+            relations = []
+            for attr in list(edited_attributes):
+                if attr == 'eid':
+                    continue
+                rschema = eschema.subjrels[attr]
+                if rschema.final:
+                    if getattr(eschema.rdef(attr), 'fulltextindexed', False):
+                        need_fti_update = True
+                    only_inline_rels = False
+                else:
+                    # inlined relation
+                    previous_value = entity.related(attr) or None
+                    if previous_value is not None:
+                        previous_value = previous_value[0][0] # got a result set
+                        if previous_value == entity[attr]:
+                            previous_value = None
+                        else:
+                            hm.call_hooks('before_delete_relation', session,
+                                          eidfrom=entity.eid, rtype=attr,
+                                          eidto=previous_value)
+                    relations.append((attr, entity[attr], previous_value))
+                source = self.source_from_eid(entity.eid, session)
+                if source.should_call_hooks:
+                    # call hooks for inlined relations
+                    for attr, value, _ in relations:
+                        hm.call_hooks('before_add_relation', session,
+                                      eidfrom=entity.eid, rtype=attr, eidto=value)
+                    if not only_inline_rels:
+                        hm.call_hooks('before_update_entity', session, entity=entity)
+                source.update_entity(session, entity)
+            self.system_source.update_info(session, entity, need_fti_update)
+            if source.should_call_hooks:
+                if not only_inline_rels:
+                    hm.call_hooks('after_update_entity', session, entity=entity)
+                for attr, value, prevvalue in relations:
+                    # if the relation is already cached, update existant cache
+                    relcache = entity.relation_cached(attr, 'subject')
+                    if prevvalue is not None:
+                        hm.call_hooks('after_delete_relation', session,
+                                      eidfrom=entity.eid, rtype=attr, eidto=prevvalue)
+                        if relcache is not None:
+                            session.update_rel_cache_del(entity.eid, attr, prevvalue)
+                    del_existing_rel_if_needed(session, entity.eid, attr, value)
+                    if relcache is not None:
+                        session.update_rel_cache_add(entity.eid, attr, value)
                     else:
-                        self.hm.call_hooks('before_delete_relation', session,
-                                           eidfrom=entity.eid, rtype=attr,
-                                           eidto=previous_value)
-                relations.append((attr, entity[attr], previous_value))
-        source = self.source_from_eid(entity.eid, session)
-        if source.should_call_hooks:
-            # call hooks for inlined relations
-            for attr, value, _ in relations:
-                self.hm.call_hooks('before_add_relation', session,
-                                    eidfrom=entity.eid, rtype=attr, eidto=value)
-            if not only_inline_rels:
-                self.hm.call_hooks('before_update_entity', session, entity=entity)
-        source.update_entity(session, entity)
-        self.system_source.update_info(session, entity, need_fti_update)
-        if source.should_call_hooks:
-            if not only_inline_rels:
-                self.hm.call_hooks('after_update_entity', session, entity=entity)
-            for attr, value, prevvalue in relations:
-                # if the relation is already cached, update existant cache
-                relcache = entity.relation_cached(attr, 'subject')
-                if prevvalue is not None:
-                    self.hm.call_hooks('after_delete_relation', session,
-                                       eidfrom=entity.eid, rtype=attr, eidto=prevvalue)
-                    if relcache is not None:
-                        session.update_rel_cache_del(entity.eid, attr, prevvalue)
-                del_existing_rel_if_needed(session, entity.eid, attr, value)
-                if relcache is not None:
-                    session.update_rel_cache_add(entity.eid, attr, value)
-                else:
-                    entity.set_related_cache(attr, 'subject',
-                                             session.eid_rset(value))
-                self.hm.call_hooks('after_add_relation', session,
-                                    eidfrom=entity.eid, rtype=attr, eidto=value)
+                        entity.set_related_cache(attr, 'subject',
+                                                 session.eid_rset(value))
+                    hm.call_hooks('after_add_relation', session,
+                                  eidfrom=entity.eid, rtype=attr, eidto=value)
+        finally:
+            if orig_edited_attributes is not None:
+                entity.edited_attributes = orig_edited_attributes
 
     def glob_delete_entity(self, session, eid):
         """delete an entity and all related entities from the repository"""
--- a/server/session.py	Tue Mar 30 14:32:03 2010 +0200
+++ b/server/session.py	Wed Mar 31 15:39:09 2010 +0200
@@ -278,8 +278,13 @@
         """return a sql cursor on the system database"""
         if not sql.split(None, 1)[0].upper() == 'SELECT':
             self.mode = 'write'
-        return self.pool.source('system').doexec(self, sql, args,
-                                                 rollback=rollback_on_failure)
+        source = self.pool.source('system')
+        try:
+            return source.doexec(self, sql, args, rollback=rollback_on_failure)
+        except (source.OperationalError, source.InterfaceError):
+            source.warning("trying to reconnect")
+            self.pool.reconnect(self)
+            return source.doexec(self, sql, args, rollback=rollback_on_failure)
 
     def set_language(self, language):
         """i18n configuration for translation"""
--- a/server/sources/native.py	Tue Mar 30 14:32:03 2010 +0200
+++ b/server/sources/native.py	Wed Mar 31 15:39:09 2010 +0200
@@ -410,7 +410,7 @@
             cursor = self.doexec(session, sql, args)
         except (self.OperationalError, self.InterfaceError):
             # FIXME: better detection of deconnection pb
-            self.info("request failed '%s' ... retry with a new cursor", sql)
+            self.warning("trying to reconnect")
             session.pool.reconnect(self)
             cursor = self.doexec(session, sql, args)
         results = self.process_result(cursor, cbs)
@@ -1031,8 +1031,8 @@
             entity = self.repo.vreg['etypes'].etype_class(etype)(session)
         except Exception:
             return [session._(
-                "Can't undo creation of entity %s of type %s, type "
-                "no more supported" % (eid, etype))]
+                "Can't undo creation of entity %(eid)s of type %(etype)s, type "
+                "no more supported" % {'eid': eid, 'etype': etype})]
         entity.set_eid(eid)
         # for proper eid/type cache update
         hook.set_operation(session, 'pendingeids', eid,
@@ -1075,8 +1075,8 @@
             cu = self.doexec(session, sql)
             if cu.fetchone() is None:
                 errors.append(session._(
-                    "Can't undo addition of relation %s from %s to %s, doesn't "
-                    "exist anymore" % (rtype, subj, obj)))
+                    "Can't undo addition of relation %(rtype)s from %(subj)s to"
+                    " %(obj)s, doesn't exist anymore" % locals()))
         if not errors:
             self.repo.hm.call_hooks('before_delete_relation', session,
                                     eidfrom=subj, rtype=rtype, eidto=obj)
--- a/server/test/unittest_hook.py	Tue Mar 30 14:32:03 2010 +0200
+++ b/server/test/unittest_hook.py	Wed Mar 31 15:39:09 2010 +0200
@@ -81,7 +81,7 @@
         raise HookCalled()
 
 
-class HooksManagerTC(TestCase):
+class HooksRegistryTC(TestCase):
 
     def setUp(self):
         """ called before each test from this class """
@@ -115,7 +115,6 @@
                          is_hook_activated=lambda x, cls: cls.category not in dis)
         self.assertRaises(HookCalled,
                           self.o.call_hooks, 'before_add_entity', cw)
-        self.o.call_hooks('before_delete_entity', cw) # nothing to call
         dis.add('cat1')
         self.o.call_hooks('before_add_entity', cw) # disabled hooks category, not called
         dis.remove('cat1')
--- a/server/test/unittest_repository.py	Tue Mar 30 14:32:03 2010 +0200
+++ b/server/test/unittest_repository.py	Wed Mar 31 15:39:09 2010 +0200
@@ -6,6 +6,9 @@
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
+from __future__ import with_statement
+
+from __future__ import with_statement
 
 import os
 import sys
@@ -20,12 +23,14 @@
 
 from cubicweb import (BadConnectionId, RepositoryError, ValidationError,
                       UnknownEid, AuthenticationError)
+from cubicweb.selectors import implements
 from cubicweb.schema import CubicWebSchema, RQLConstraint
 from cubicweb.dbapi import connect, multiple_connections_unfix
 from cubicweb.devtools.testlib import CubicWebTC
 from cubicweb.devtools.repotest import tuplify
 from cubicweb.server import repository, hook
 from cubicweb.server.sqlutils import SQL_PREFIX
+from cubicweb.server.hook import Hook
 from cubicweb.server.sources import native
 
 # start name server anyway, process will fail if already running
@@ -272,8 +277,10 @@
             from logilab.common import pyro_ext
             pyro_ext._DAEMONS.clear()
 
+
     def _pyro_client(self, done):
-        cnx = connect(self.repo.config.appid, u'admin', password='gingkow')
+        cnx = connect(self.repo.config.appid, u'admin', password='gingkow',
+                      initlog=False) # don't reset logging configuration
         try:
             # check we can get the schema
             schema = cnx.get_schema()
@@ -283,7 +290,7 @@
             cnx.close()
             done.append(True)
         finally:
-            # connect monkey path some method by default, remove them
+            # connect monkey patch some method by default, remove them
             multiple_connections_unfix()
 
     def test_internal_api(self):
@@ -357,6 +364,42 @@
         self.assertEquals(rset.rows[0][0], p2.eid)
 
 
+    def test_set_attributes_in_before_update(self):
+        # local hook
+        class DummyBeforeHook(Hook):
+            __regid__ = 'dummy-before-hook'
+            __select__ = Hook.__select__ & implements('EmailAddress')
+            events = ('before_update_entity',)
+            def __call__(self):
+                # safety belt: avoid potential infinite recursion if the test
+                #              fails (i.e. RuntimeError not raised)
+                pendings = self._cw.transaction_data.setdefault('pending', set())
+                if self.entity.eid not in pendings:
+                    pendings.add(self.entity.eid)
+                    self.entity.set_attributes(alias=u'foo')
+        with self.temporary_appobjects(DummyBeforeHook):
+            req = self.request()
+            addr = req.create_entity('EmailAddress', address=u'a@b.fr')
+            addr.set_attributes(address=u'a@b.com')
+            rset = self.execute('Any A,AA WHERE X eid %(x)s, X address A, X alias AA',
+                                {'x': addr.eid})
+            self.assertEquals(rset.rows, [[u'a@b.com', u'foo']])
+
+    def test_set_attributes_in_before_add(self):
+        # local hook
+        class DummyBeforeHook(Hook):
+            __regid__ = 'dummy-before-hook'
+            __select__ = Hook.__select__ & implements('EmailAddress')
+            events = ('before_add_entity',)
+            def __call__(self):
+                # set_attributes is forbidden within before_add_entity()
+                self.entity.set_attributes(alias=u'foo')
+        with self.temporary_appobjects(DummyBeforeHook):
+            req = self.request()
+            self.assertRaises(RepositoryError, req.create_entity,
+                              'EmailAddress', address=u'a@b.fr')
+
+
 class DataHelpersTC(CubicWebTC):
 
     def test_create_eid(self):
@@ -402,6 +445,7 @@
 class FTITC(CubicWebTC):
 
     def test_reindex_and_modified_since(self):
+        self.repo.system_source.multisources_etypes.add('Personne')
         eidp = self.execute('INSERT Personne X: X nom "toto", X prenom "tutu"')[0][0]
         self.commit()
         ts = datetime.now()
@@ -484,7 +528,6 @@
     """
     def setUp(self):
         CubicWebTC.setUp(self)
-        self.hm = self.repo.hm
         CALLED[:] = ()
 
     def _after_relation_hook(self, pool, fromeid, rtype, toeid):
@@ -500,20 +543,20 @@
             def __call__(self):
                 CALLED.append((self.event, self.eidfrom, self.rtype, self.eidto))
 
-        self.hm.register(EcritParHook)
-        eidp = self.execute('INSERT Personne X: X nom "toto"')[0][0]
-        eidn = self.execute('INSERT Note X: X type "T"')[0][0]
-        self.execute('SET N ecrit_par Y WHERE N type "T", Y nom "toto"')
-        self.assertEquals(CALLED, [('before_add_relation', eidn, 'ecrit_par', eidp),
-                                   ('after_add_relation', eidn, 'ecrit_par', eidp)])
-        CALLED[:] = ()
-        self.execute('DELETE N ecrit_par Y WHERE N type "T", Y nom "toto"')
-        self.assertEquals(CALLED, [('before_delete_relation', eidn, 'ecrit_par', eidp),
-                                   ('after_delete_relation', eidn, 'ecrit_par', eidp)])
-        CALLED[:] = ()
-        eidn = self.execute('INSERT Note N: N ecrit_par P WHERE P nom "toto"')[0][0]
-        self.assertEquals(CALLED, [('before_add_relation', eidn, 'ecrit_par', eidp),
-                                   ('after_add_relation', eidn, 'ecrit_par', eidp)])
+        with self.temporary_appobjects(EcritParHook):
+            eidp = self.execute('INSERT Personne X: X nom "toto"')[0][0]
+            eidn = self.execute('INSERT Note X: X type "T"')[0][0]
+            self.execute('SET N ecrit_par Y WHERE N type "T", Y nom "toto"')
+            self.assertEquals(CALLED, [('before_add_relation', eidn, 'ecrit_par', eidp),
+                                       ('after_add_relation', eidn, 'ecrit_par', eidp)])
+            CALLED[:] = ()
+            self.execute('DELETE N ecrit_par Y WHERE N type "T", Y nom "toto"')
+            self.assertEquals(CALLED, [('before_delete_relation', eidn, 'ecrit_par', eidp),
+                                       ('after_delete_relation', eidn, 'ecrit_par', eidp)])
+            CALLED[:] = ()
+            eidn = self.execute('INSERT Note N: N ecrit_par P WHERE P nom "toto"')[0][0]
+            self.assertEquals(CALLED, [('before_add_relation', eidn, 'ecrit_par', eidp),
+                                       ('after_add_relation', eidn, 'ecrit_par', eidp)])
 
 
 if __name__ == '__main__':
--- a/server/test/unittest_storage.py	Tue Mar 30 14:32:03 2010 +0200
+++ b/server/test/unittest_storage.py	Wed Mar 31 15:39:09 2010 +0200
@@ -6,6 +6,8 @@
 :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 
+from __future__ import with_statement
+
 from logilab.common.testlib import unittest_main
 from cubicweb.devtools.testlib import CubicWebTC
 
@@ -93,14 +95,8 @@
         self.assertEquals(fspath.getvalue(), '/the/path')
 
     def test_source_storage_transparency(self):
-        self.vreg._loadedmods[__name__] = {}
-        self.vreg.register(DummyBeforeHook)
-        self.vreg.register(DummyAfterHook)
-        try:
+        with self.temporary_appobjects(DummyBeforeHook, DummyAfterHook):
             self.create_file()
-        finally:
-            self.vreg.unregister(DummyBeforeHook)
-            self.vreg.unregister(DummyAfterHook)
 
     def test_source_mapped_attribute_error_cases(self):
         ex = self.assertRaises(QueryError, self.execute,
--- a/server/test/unittest_undo.py	Tue Mar 30 14:32:03 2010 +0200
+++ b/server/test/unittest_undo.py	Wed Mar 31 15:39:09 2010 +0200
@@ -64,7 +64,7 @@
         self.assertEquals(a4.eid_to, self.toto.in_group[0].eid)
         self.assertEquals(a4.order, 4)
         for i, rtype in ((1, 'owned_by'), (2, 'owned_by'),
-                         (4, 'created_by'), (5, 'in_state')):
+                         (4, 'in_state'), (5, 'created_by')):
             a = actions[i]
             self.assertEquals(a.action, 'A')
             self.assertEquals(a.eid_from, self.toto.eid)
--- a/vregistry.py	Tue Mar 30 14:32:03 2010 +0200
+++ b/vregistry.py	Wed Mar 31 15:39:09 2010 +0200
@@ -82,6 +82,11 @@
         return cls.id
     return cls.__regid__
 
+def class_registries(cls, registryname):
+    if registryname:
+        return (registryname,)
+    return cls.__registries__
+
 
 class Registry(dict):
 
@@ -205,25 +210,25 @@
         if len(args) > 1:
             warn('[3.5] only the request param can not be named when calling select*',
                  DeprecationWarning, stacklevel=3)
-        score, winners = 0, []
+        score, winners = 0, None
         for appobject in appobjects:
             appobjectscore = appobject.__select__(appobject, *args, **kwargs)
             if appobjectscore > score:
                 score, winners = appobjectscore, [appobject]
             elif appobjectscore > 0 and appobjectscore == score:
                 winners.append(appobject)
-        if not winners:
+        if winners is None:
             raise NoSelectableObject('args: %s\nkwargs: %s %s'
                                      % (args, kwargs.keys(),
                                         [repr(v) for v in appobjects]))
         if len(winners) > 1:
+            # log in production environement, error while debugging
             if self.config.debugmode:
-                self.error('select ambiguity, args: %s\nkwargs: %s %s',
-                           args, kwargs.keys(), [repr(v) for v in winners])
-            else:
                 raise Exception('select ambiguity, args: %s\nkwargs: %s %s'
                                 % (args, kwargs.keys(),
                                    [repr(v) for v in winners]))
+            self.error('select ambiguity, args: %s\nkwargs: %s %s',
+                       args, kwargs.keys(), [repr(v) for v in winners])
         # return the result of calling the appobject
         return winners[0](*args, **kwargs)
 
@@ -318,31 +323,33 @@
                 if obj.__module__ != modname or obj in butclasses:
                     continue
                 oid = class_regid(obj)
-                registryname = obj.__registry__
             except AttributeError:
                 continue
             if oid and not '__abstract__' in obj.__dict__:
-                self.register(obj, registryname)
+                self.register(obj, oid=oid)
 
     def register(self, obj, registryname=None, oid=None, clear=False):
         """base method to add an object in the registry"""
         assert not '__abstract__' in obj.__dict__
-        registryname = registryname or obj.__registry__
-        registry = self.setdefault(registryname)
-        registry.register(obj, oid=oid, clear=clear)
         try:
             vname = obj.__name__
         except AttributeError:
+            # XXX may occurs?
             vname = obj.__class__.__name__
-        self.debug('registered appobject %s in registry %s with id %s',
-                   vname, registryname, oid or class_regid(obj))
+        for registryname in class_registries(obj, registryname):
+            registry = self.setdefault(registryname)
+            registry.register(obj, oid=oid, clear=clear)
+            self.debug('registered appobject %s in registry %s with id %s',
+                       vname, registryname, oid or class_regid(obj))
         self._loadedmods[obj.__module__][classid(obj)] = obj
 
     def unregister(self, obj, registryname=None):
-        self[registryname or obj.__registry__].unregister(obj)
+        for registryname in class_registries(obj, registryname):
+            self[registryname].unregister(obj)
 
     def register_and_replace(self, obj, replaced, registryname=None):
-        self[registryname or obj.__registry__].register_and_replace(obj, replaced)
+        for registryname in class_registries(obj, registryname):
+            self[registryname].register_and_replace(obj, replaced)
 
     # initialization methods ###################################################
 
@@ -451,7 +458,7 @@
             self._load_ancestors_then_object(modname, parent)
         if (appobjectcls.__dict__.get('__abstract__')
             or appobjectcls.__name__[0] == '_'
-            or not appobjectcls.__registry__
+            or not appobjectcls.__registries__
             or not class_regid(appobjectcls)):
             return
         try:
--- a/web/data/cubicweb.edition.js	Tue Mar 30 14:32:03 2010 +0200
+++ b/web/data/cubicweb.edition.js	Wed Mar 31 15:39:09 2010 +0200
@@ -331,26 +331,30 @@
     var firsterrfield = null;
     for (fieldname in errors) {
 	var errmsg = errors[fieldname];
-	var fieldid = fieldname + ':' + eid;
-	var suffixes = ['', '-subject', '-object'];
-	var found = false;
-	// XXX remove suffixes at some point
-	for (var i=0, length=suffixes.length; i<length;i++) {
-	    var field = jqNode(fieldname + suffixes[i] + ':' + eid);
-	    if (field && getNodeAttribute(field, 'type') != 'hidden') {
-		if ( !firsterrfield ) {
-		    firsterrfield = 'err-' + fieldid;
+	if (!fieldname) {
+	    globalerrors.push(errmsg);
+	} else {
+	    var fieldid = fieldname + ':' + eid;
+	    var suffixes = ['', '-subject', '-object'];
+	    var found = false;
+	    // XXX remove suffixes at some point
+	    for (var i=0, length=suffixes.length; i<length;i++) {
+		var field = jqNode(fieldname + suffixes[i] + ':' + eid);
+		if (field && getNodeAttribute(field, 'type') != 'hidden') {
+		    if ( !firsterrfield ) {
+			firsterrfield = 'err-' + fieldid;
+		    }
+		    addElementClass(field, 'error');
+		    var span = SPAN({'id': 'err-' + fieldid, 'class': "errorMsg"}, errmsg);
+		    field.before(span);
+		    found = true;
+		    break;
 		}
-		addElementClass(field, 'error');
-		var span = SPAN({'id': 'err-' + fieldid, 'class': "errorMsg"}, errmsg);
-		field.before(span);
-		found = true;
-		break;
 	    }
-	}
-	if (!found) {
-	    firsterrfield = formid;
-	    globalerrors.push(_(fieldname) + ' : ' + errmsg);
+	    if (!found) {
+		firsterrfield = formid;
+		globalerrors.push(_(fieldname) + ' : ' + errmsg);
+	    }
 	}
     }
     if (globalerrors.length) {
--- a/web/data/cubicweb.facets.js	Tue Mar 30 14:32:03 2010 +0200
+++ b/web/data/cubicweb.facets.js	Wed Mar 31 15:39:09 2010 +0200
@@ -47,10 +47,11 @@
 	var rql = result[0];
 	var $bkLink = jQuery('#facetBkLink');
 	if ($bkLink.length) {
-	    var bkUrl = $bkLink.attr('cubicweb:target') + '&path=view?rql=' + rql;
+	    var bkPath = 'view?rql=' + escape(rql);
 	    if (vid) {
-		bkUrl += '&vid=' + vid;
+		bkPath += '&vid=' + escape(vid);
 	    }
+	    var bkUrl = $bkLink.attr('cubicweb:target') + '&path=' + escape(bkPath);
 	    $bkLink.attr('href', bkUrl);
 	}
 	var toupdate = result[1];
--- a/web/formfields.py	Tue Mar 30 14:32:03 2010 +0200
+++ b/web/formfields.py	Wed Mar 31 15:39:09 2010 +0200
@@ -867,7 +867,7 @@
             eids.add(typed_eid)
         return eids
 
-
+# XXX use cases where we don't actually want a better widget?
 class CompoundField(Field):
     def __init__(self, fields, *args, **kwargs):
         super(CompoundField, self).__init__(*args, **kwargs)
@@ -877,7 +877,11 @@
         return self.fields
 
     def actual_fields(self, form):
-        return [self] + list(self.fields)
+        # don't add [self] to actual fields, compound field is usually kinda
+        # virtual, all interesting values are in subfield. Skipping it may avoid
+        # error when processed by the editcontroller : it may be marked as required
+        # while it has no value, hence generating a false error.
+        return list(self.fields)
 
 
 _AFF_KWARGS = uicfg.autoform_field_kwargs
--- a/web/formwidgets.py	Tue Mar 30 14:32:03 2010 +0200
+++ b/web/formwidgets.py	Wed Mar 31 15:39:09 2010 +0200
@@ -783,6 +783,7 @@
             attrs['id'] = field.dom_id(form, 'fqs')
         if self.settabindex:
             attrs['tabindex'] = req.next_tabindex()
+        attrs.setdefault('cols', 60)
         attrs.setdefault('onkeyup', 'autogrow(this)')
         inputs += [tags.textarea(fqs, name=fqsqname, **attrs),
                    u'</td></tr></table>']
--- a/web/views/basecontrollers.py	Tue Mar 30 14:32:03 2010 +0200
+++ b/web/views/basecontrollers.py	Wed Mar 31 15:39:09 2010 +0200
@@ -282,8 +282,6 @@
         except RemoteCallFailed:
             raise
         except Exception, ex:
-            import traceback
-            traceback.print_exc()
             self.exception('an exception occured while calling js_%s(%s): %s',
                            fname, args, ex)
             raise RemoteCallFailed(repr(ex))
@@ -392,7 +390,18 @@
         else: # we receive unicode keys which is not supported by the **syntax
             extraargs = dict((str(key), value)
                              for key, value in extraargs.items())
-        comp = self._cw.vreg[registry].select(compid, self._cw, rset=rset, **extraargs)
+        # XXX while it sounds good, addition of the try/except below cause pb:
+        # when filtering using facets return an empty rset, the edition box
+        # isn't anymore selectable, as expected. The pb is that with the
+        # try/except below, we see a "an error occured" message in the ui, while
+        # we don't see it without it. Proper fix would probably be to deal with
+        # this by allowing facet handling code to tell to js_component that such
+        # error is expected and should'nt be reported.
+        #try:
+        comp = self._cw.vreg[registry].select(compid, self._cw, rset=rset,
+                                              **extraargs)
+        #except NoSelectableObject:
+        #    raise RemoteCallFailed('unselectable')
         extraargs = extraargs or {}
         stream = comp.set_stream()
         comp.render(**extraargs)
--- a/web/views/bookmark.py	Tue Mar 30 14:32:03 2010 +0200
+++ b/web/views/bookmark.py	Wed Mar 31 15:39:09 2010 +0200
@@ -113,7 +113,7 @@
                     # we can't edit shared bookmarks we don't own
                     bookmarksrql = 'Bookmark B WHERE B bookmarked_by U, B owned_by U, U eid %(x)s'
                     erset = req.execute(bookmarksrql, {'x': ueid}, 'x',
-                                                build_descr=False)
+                                        build_descr=False)
                     bookmarksrql %= {'x': ueid}
                 if erset:
                     url = self._cw.build_url(vid='muledit', rql=bookmarksrql)
--- a/web/views/facets.py	Tue Mar 30 14:32:03 2010 +0200
+++ b/web/views/facets.py	Wed Mar 31 15:39:09 2010 +0200
@@ -105,7 +105,10 @@
     def display_bookmark_link(self, rset):
         eschema = self._cw.vreg.schema.eschema('Bookmark')
         if eschema.has_perm(self._cw, 'add'):
-            bk_path = 'view?rql=%s' % rset.printable_rql()
+            bk_path = 'rql=%s' % self._cw.url_quote(rset.printable_rql())
+            if self._cw.form.get('vid'):
+                bk_path += '&vid=%s' % self._cw.url_quote(self._cw.form['vid'])
+            bk_path = 'view?' + bk_path
             bk_title = self._cw._('my custom search')
             linkto = 'bookmarked_by:%s:subject' % self._cw.user.eid
             bk_add_url = self._cw.build_url('add/Bookmark', path=bk_path, title=bk_title, __linkto=linkto)