merge heads in tls-sprint branch tls-sprint
authorAdrien Di Mascio <Adrien.DiMascio@logilab.fr>
Tue, 17 Feb 2009 17:28:25 +0100
branchtls-sprint
changeset 677 9b04bbfe3f90
parent 667 15fe1d3694d1 (diff)
parent 675 eab61515d76a (current diff)
child 678 4a29050fe143
merge heads in tls-sprint branch
--- a/common/appobject.py	Tue Feb 17 16:25:20 2009 +0100
+++ b/common/appobject.py	Tue Feb 17 17:28:25 2009 +0100
@@ -23,7 +23,6 @@
 from cubicweb.common.registerers import yes_registerer, priority_registerer
 from cubicweb.common.selectors import yes
 
-_MARKER = object()
 
 
 class Cache(dict):    
@@ -73,6 +72,7 @@
         instance = cls(req, rset)
         instance.row = row
         instance.col = col
+        instance.sel_kwargs = kwargs
         return instance
 
     # Eproperties definition:
@@ -323,56 +323,3 @@
         self.req = req
         self.rset = rset
         self.__dict__.update(kwargs)
-
-
-class ReloadableMixIn(object):
-    """simple mixin for reloadable parts of UI"""
-    
-    def user_callback(self, cb, args, msg=None, nonify=False):
-        """register the given user callback and return an url to call it ready to be
-        inserted in html
-        """
-        self.req.add_js('cubicweb.ajax.js')
-        if nonify:
-            _cb = cb
-            def cb(*args):
-                _cb(*args)
-        cbname = self.req.register_onetime_callback(cb, *args)
-        return self.build_js(cbname, html_escape(msg or ''))
-        
-    def build_update_js_call(self, cbname, msg):
-        rql = html_escape(self.rset.printable_rql())
-        return "javascript:userCallbackThenUpdateUI('%s', '%s', '%s', '%s', '%s', '%s')" % (
-            cbname, self.id, rql, msg, self.__registry__, self.div_id())
-    
-    def build_reload_js_call(self, cbname, msg):
-        return "javascript:userCallbackThenReloadPage('%s', '%s')" % (cbname, msg)
-
-    build_js = build_update_js_call # expect updatable component by default
-    
-    def div_id(self):
-        return ''
-
-
-class ComponentMixIn(ReloadableMixIn):
-    """simple mixin for component object"""
-    __registry__ = 'components'
-    __registerer__ = yes_registerer
-    __selectors__ = (yes,)
-    __select__ = classmethod(*__selectors__)
-
-    def div_class(self):
-        return '%s %s' % (self.propval('htmlclass'), self.id)
-
-    def div_id(self):
-        return '%sComponent' % self.id
-
-
-class Component(ComponentMixIn, AppObject):
-    """base class for non displayable components
-    """
-
-class SingletonComponent(Component):
-    """base class for non displayable unique components
-    """
-    __registerer__ = priority_registerer
--- a/common/registerers.py	Tue Feb 17 16:25:20 2009 +0100
+++ b/common/registerers.py	Tue Feb 17 17:28:25 2009 +0100
@@ -84,18 +84,6 @@
         # remove it latter if no object is implementing accepted interfaces
         if _accepts_interfaces(self.vobject):
             return self.vobject
-# XXX no more .accepts attribute    
-#         if not 'Any' in self.vobject.accepts:
-#             for ertype in self.vobject.accepts:
-#                 if ertype in self.schema:
-#                     break
-#             else:
-#                 self.skip()
-#                 return None
-        for required in getattr(self.vobject, 'requires', ()):
-            if required not in self.schema:
-                self.skip()
-                return
         self.remove_equivalents(registered)
         return self.vobject
     
@@ -185,20 +173,6 @@
             registerer = accepts_registerer(self.registry, cls)
             cls = registerer.do_it_yourself(registered)
         return cls
-
-
-class extresources_registerer(priority_registerer):
-    """'registerer according to a .need_resources attributes which
-    should list necessary resource identifiers for the wrapped object.
-    If one of its resources is missing, don't register
-    """
-    def do_it_yourself(self, registered):
-        if not hasattr(self.config, 'has_resource'):
-            return
-        for resourceid in self.vobject.need_resources:
-            if not self.config.has_resource(resourceid):
-                return
-        return super(extresources_registerer, self).do_it_yourself(registered)
     
 
 __all__ = [cls.__name__ for cls in globals().values()
--- a/common/view.py	Tue Feb 17 16:25:20 2009 +0100
+++ b/common/view.py	Tue Feb 17 17:28:25 2009 +0100
@@ -69,6 +69,8 @@
 
 STRICT_DOCTYPE = u'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd" %s>\n'
 
+# base view object ############################################################
+
 class View(AppRsetObject):
     """abstract view class, used as base for every renderable object such
     as views, templates, some components...web
@@ -418,7 +420,7 @@
     is only used globally (i.e. no result set adaptation)
     """
     __registry__ = 'templates'
-    __registerer__ = priority_registerer
+    # __registerer__ = priority_registerer
     __selectors__ = (yes,)
 
     registered = require_group_compat(View.registered.im_func)
@@ -466,3 +468,50 @@
         self._stream.doctype = self.doctype
         if not xmldecl:
             self._stream.xmldecl = u''
+
+# concrete component base classes #############################################
+
+class ReloadableMixIn(object):
+    """simple mixin for reloadable parts of UI"""
+    
+    def user_callback(self, cb, args, msg=None, nonify=False):
+        """register the given user callback and return an url to call it ready to be
+        inserted in html
+        """
+        self.req.add_js('cubicweb.ajax.js')
+        if nonify:
+            _cb = cb
+            def cb(*args):
+                _cb(*args)
+        cbname = self.req.register_onetime_callback(cb, *args)
+        return self.build_js(cbname, html_escape(msg or ''))
+        
+    def build_update_js_call(self, cbname, msg):
+        rql = html_escape(self.rset.printable_rql())
+        return "javascript:userCallbackThenUpdateUI('%s', '%s', '%s', '%s', '%s', '%s')" % (
+            cbname, self.id, rql, msg, self.__registry__, self.div_id())
+    
+    def build_reload_js_call(self, cbname, msg):
+        return "javascript:userCallbackThenReloadPage('%s', '%s')" % (cbname, msg)
+
+    build_js = build_update_js_call # expect updatable component by default
+    
+    def div_id(self):
+        return ''
+
+
+class Component(ReloadableMixIn, View):
+    """base class for components"""
+    __registry__ = 'components'
+    __registerer__ = yes_registerer
+    __selectors__ = (yes,)
+    property_defs = {
+        _('visible'):  dict(type='Boolean', default=True,
+                            help=_('display the box or not')),
+        }    
+
+    def div_class(self):
+        return '%s %s' % (self.propval('htmlclass'), self.id)
+
+    def div_id(self):
+        return '%sComponent' % self.id
--- a/cwvreg.py	Tue Feb 17 16:25:20 2009 +0100
+++ b/cwvreg.py	Tue Feb 17 17:28:25 2009 +0100
@@ -48,8 +48,10 @@
     def reset(self):
         self._registries = {}
         self._lastmodifs = {}
-        # two special registries, propertydefs which care all the property definitions, and
-        # propertyvals which contains values for those properties
+        self._needs_iface = {}
+        # two special registries, propertydefs which care all the property
+        # definitions, and propertyvals which contains values for those
+        # properties
         self._registries['propertydefs'] = {}
         self._registries['propertyvalues'] = self.eprop_values = {}
         for key, propdef in self.config.eproperty_definitions():
@@ -73,42 +75,42 @@
             for objects in regcontent.values():
                 for obj in objects:
                     obj.schema = schema
+
+    def register_if_interface_found(self, obj, ifaces, **kwargs):
+        """register an object but remove it if no entity class implements one of
+        the given interfaces
+        """
+        if not isinstance(ifaces,  (tuple, list)):
+            self._needs_iface[obj] = frozenset((ifaces,))
+        else:
+            self._needs_iface[obj] = frozenset(ifaces)
+        self.register(obj, **kwargs)
+
+    def register(self, obj, **kwargs):
+        super(CubicWebRegistry, self).register(obj, **kwargs)
+        # XXX bw compat
+        ifaces = getattr(obj, 'accepts_interfaces', None)
+        if ifaces:
+            self._needs_iface[obj] = frozenset(ifaces)
         
     def register_objects(self, path, force_reload=None):
-        """overriden to handle type class cache issue"""
-        if  super(CubicWebRegistry, self).register_objects(path, force_reload):
+        """overriden to remove objects requiring a missing interface"""
+        if super(CubicWebRegistry, self).register_objects(path, force_reload):
             # clear etype cache if you don't want to run into deep weirdness
             clear_cache(self, 'etype_class')
+            # we may want to keep interface dependent objects (e.g.for i18n
+            # catalog generation)
+            if not self.config.cleanup_interface_sobjects:
+                return
             # remove vobjects that don't support any available interface
             interfaces = set()
             for classes in self.get('etypes', {}).values():
                 for cls in classes:
                     interfaces.update(cls.__implements__)
-            if not self.config.cleanup_interface_sobjects:
-                return
-            for registry, regcontent in self._registries.items():
-                if registry in ('propertydefs', 'propertyvalues', 'etypes'):
-                    continue
-                for oid, objects in regcontent.items():
-                    for obj in reversed(objects[:]):
-                        if not obj in objects:
-                            continue # obj has been kicked by a previous one
-                        accepted = set(getattr(obj, 'accepts_interfaces', ()))
-                        if accepted:
-                            for accepted_iface in accepted:
-                                for found_iface in interfaces:
-                                    if issubclass(found_iface, accepted_iface):
-                                        # consider priority if necessary
-                                        if hasattr(obj.__registerer__, 'remove_all_equivalents'):
-                                            registerer = obj.__registerer__(self, obj)
-                                            registerer.remove_all_equivalents(objects)
-                                        break
-                                else:
-                                    self.debug('kicking vobject %s (unsupported interface)', obj)
-                                    objects.remove(obj)
-                    # if objects is empty, remove oid from registry
-                    if not objects:
-                        del regcontent[oid]
+            for obj, ifaces in self._needs_iface.items():
+                if not ifaces & interfaces:
+                    self.debug('kicking vobject %s (unsupported interface)', obj)
+                    self.unregister(obj)
 
     def eid_rset(self, cursor, eid, etype=None):
         """return a result set for the given eid without doing actual query
--- a/selectors.py	Tue Feb 17 16:25:20 2009 +0100
+++ b/selectors.py	Tue Feb 17 17:28:25 2009 +0100
@@ -483,12 +483,16 @@
         self.expected_ifaces = expected_ifaces
 
     def score_class(self, eclass, req):
+        print '***********************************'
         score = 0
         for iface in self.expected_ifaces:
+            print 'TESTING', iface, 'on', eclass
             if isinstance(iface, basestring):
                 # entity type
                 iface = eclass.vreg.etype_class(iface)
+                print 'found iface ===', iface
             if implements_iface(eclass, iface):
+                print 'and implementing !!!'
                 score += 1
                 if getattr(iface, '__registry__', None) == 'etypes':
                     score += 1
@@ -504,6 +508,7 @@
                                 score += index
 #                                print 'etype majoration', index
                                 break
+        print '***********************************', score
         return score
 
 
--- a/sobjects/notification.py	Tue Feb 17 16:25:20 2009 +0100
+++ b/sobjects/notification.py	Tue Feb 17 17:28:25 2009 +0100
@@ -18,8 +18,7 @@
 from logilab.common.textutils import normalize_text
 
 from cubicweb import RegistryException
-from cubicweb.common.view import EntityView
-from cubicweb.common.appobject import Component
+from cubicweb.common.view import EntityView, Component
 from cubicweb.common.registerers import accepts_registerer
 from cubicweb.common.selectors import implements
 from cubicweb.common.mail import format_mail
--- a/sobjects/supervising.py	Tue Feb 17 16:25:20 2009 +0100
+++ b/sobjects/supervising.py	Tue Feb 17 17:28:25 2009 +0100
@@ -8,7 +8,7 @@
 __docformat__ = "restructuredtext en"
 
 from cubicweb import UnknownEid
-from cubicweb.common.appobject import ComponentMixIn
+from cubicweb.common.appobject import Component
 from cubicweb.common.view import StartupView
 from cubicweb.common.mail import format_mail
 from cubicweb.server.hooksmanager import Hook
@@ -138,9 +138,10 @@
             yield change
 
 
-class SupervisionEmailView(ComponentMixIn, StartupView):
+class SupervisionEmailView(Component):
     """view implementing the email API for data changes supervision notification
     """
+    __selectors__ = (none_rset,)
     id = 'supervision_notif'
 
     def recipients(self):
--- a/vregistry.py	Tue Feb 17 16:25:20 2009 +0100
+++ b/vregistry.py	Tue Feb 17 17:28:25 2009 +0100
@@ -204,109 +204,6 @@
 
     def __contains__(self, key):
         return key in self._registries
-        
-    def register_vobject_class(self, cls, _kicked=set()):
-        """handle vobject class registration
-        
-        vobject class with __abstract__ == True in their local dictionnary or
-        with a name starting starting by an underscore are not registered.
-        Also a vobject class needs to have __registry__ and id attributes set
-        to a non empty string to be registered.
-
-        Registration is actually handled by vobject's registerer.
-        """
-        if (cls.__dict__.get('__abstract__') or cls.__name__[0] == '_'
-            or not cls.__registry__ or not cls.id):
-            return
-        # while reloading a module :
-        # if cls was previously kicked, it means that there is a more specific
-        # vobject defined elsewhere re-registering cls would kick it out
-        if cls.classid() in _kicked:
-            self.debug('not re-registering %s because it was previously kicked',
-                      cls.classid())
-        else:
-            regname = cls.__registry__
-            if cls.id in self.config['disable-%s' % regname]:
-                return
-            registry = self._registries.setdefault(regname, {})
-            vobjects = registry.setdefault(cls.id, [])
-            registerer = cls.__registerer__(self, cls)
-            cls = registerer.do_it_yourself(vobjects)
-            #_kicked |= registerer.kicked
-            if cls:
-                # registered() is technically a classmethod but is not declared
-                # as such because we need to compose registered in some cases
-                vobject = cls.registered.im_func(cls, self)
-                try:
-                    vname = vobject.__name__
-                except AttributeError:
-                    vname = vobject.__class__.__name__
-                self.debug('registered vobject %s in registry %s with id %s',
-                          vname, cls.__registry__, cls.id)
-                vobjects.append(vobject)
-            
-    def unregister_module_vobjects(self, modname):
-        """removes registered objects coming from a given module
-
-        returns a dictionnary classid/class of all classes that will need
-        to be updated after reload (i.e. vobjects referencing classes defined
-        in the <modname> module)
-        """
-        unregistered = {}
-        # browse each registered object
-        for registry, objdict in self.items():
-            for oid, objects in objdict.items():
-                for obj in objects[:]:
-                    objname = obj.classid()
-                    # if the vobject is defined in this module, remove it
-                    if objname.startswith(modname):
-                        unregistered[objname] = obj
-                        objects.remove(obj)
-                        self.debug('unregistering %s in %s registry',
-                                  objname, registry)
-                    # if not, check if the vobject can be found in baseclasses
-                    # (because we also want subclasses to be updated)
-                    else:
-                        if not isinstance(obj, type):
-                            obj = obj.__class__
-                        for baseclass in obj.__bases__:
-                            if hasattr(baseclass, 'classid'):
-                                baseclassid = baseclass.classid()
-                                if baseclassid.startswith(modname):
-                                    unregistered[baseclassid] = baseclass
-                # update oid entry
-                if objects:
-                    objdict[oid] = objects
-                else:
-                    del objdict[oid]
-        return unregistered
-
-
-    def update_registered_subclasses(self, oldnew_mapping):
-        """updates subclasses of re-registered vobjects
-
-        if baseviews.PrimaryView is changed, baseviews.py will be reloaded
-        automatically and the new version of PrimaryView will be registered.
-        But all existing subclasses must also be notified of this change, and
-        that's what this method does
-
-        :param oldnew_mapping: a dict mapping old version of a class to
-                               the new version
-        """
-        # browse each registered object
-        for objdict in self.values():
-            for objects in objdict.values():
-                for obj in objects:
-                    if not isinstance(obj, type):
-                        obj = obj.__class__
-                    # build new baseclasses tuple
-                    newbases = tuple(oldnew_mapping.get(baseclass, baseclass)
-                                     for baseclass in obj.__bases__)
-                    # update obj's baseclasses tuple (__bases__) if needed
-                    if newbases != obj.__bases__:
-                        self.debug('updating %s.%s base classes',
-                                  obj.__module__, obj.__name__)
-                        obj.__bases__ = newbases
 
     def registry(self, name):
         """return the registry (dictionary of class objects) associated to
@@ -332,7 +229,69 @@
             for objs in registry.values():
                 result += objs
             return result
-        
+
+    def object_by_id(self, registry, cid, *args, **kwargs):
+        """return the most specific component according to the resultset"""
+        objects = self[registry][cid]
+        assert len(objects) == 1, objects
+        return objects[0].selected(*args, **kwargs)
+
+    # methods for explicit (un)registration ###################################
+    
+    def register(self, obj, registryname=None, oid=None):
+        """base method to add an object in the registry"""
+        registryname = registryname or obj.__registry__
+        oid = oid or obj.id
+        registry = self._registries.setdefault(registryname, {})
+        vobjects = registry.setdefault(oid, [])
+        # registered() is technically a classmethod but is not declared
+        # as such because we need to compose registered in some cases
+        vobject = obj.registered.im_func(cls, self)
+        assert not vobject in vobjects
+        vobjects.append(vobject)
+        try:
+            vname = vobject.__name__
+        except AttributeError:
+            vname = vobject.__class__.__name__
+        self.debug('registered vobject %s in registry %s with id %s',
+                   vname, registryname, oid)
+        # automatic reloading management
+        self._registered['%s.%s' % (obj.__module__, oid)] = obj
+
+    def unregister(self, obj, registryname=None):
+        registryname = registryname or obj.__registry__
+        registry = self.registry(registryname)
+        removed_id = obj.classid()
+        for registered in registry[obj.id]:
+            # use classid() to compare classes because vreg will probably
+            # have its own version of the class, loaded through execfile
+            if registered.classid() == removed_id:
+                # XXX automatic reloading management
+                try:
+                    registry[obj.id].remove(registered)
+                except ValueError:
+                    self.warning('can\'t remove %s, no id %s in the %s registry',
+                                 removed_id, obj.id, registryname)
+                except ValueError:
+                    self.warning('can\'t remove %s, not in the %s registry with id %s',
+                                 removed_id, registryname, obj.id)
+#                 else:
+#                     # if objects is empty, remove oid from registry
+#                     if not registry[obj.id]:
+#                         del regcontent[oid]                    
+                break
+    
+    def register_and_replace(self, obj, replaced, registryname=None):
+        registryname = registryname or obj.__registry__
+        registry = self.registry(registryname)
+        registered_objs = registry[obj.id]
+        for index, registered in enumerate(registered_objs):
+            if registered.classid() == replaced:
+                registry[obj.id][index] = obj
+                self._registered['%s.%s' % (obj.__module__, obj.id)] = obj                
+
+    # dynamic selection methods ###############################################
+    
     def select(self, vobjects, *args, **kwargs):
         """return an instance of the most specific object according
         to parameters
@@ -374,15 +333,8 @@
     def select_object(self, registry, cid, *args, **kwargs):
         """return the most specific component according to the resultset"""
         return self.select(self.registry_objects(registry, cid), *args, **kwargs)
-
-    def object_by_id(self, registry, cid, *args, **kwargs):
-        """return the most specific component according to the resultset"""
-        objects = self[registry][cid]
-        assert len(objects) == 1, objects
-        return objects[0].selected(*args, **kwargs)
     
     # intialization methods ###################################################
-
     
     def register_objects(self, path, force_reload=None):
         if force_reload is None:
@@ -473,15 +425,18 @@
         return True
 
     def load_module(self, module):
-        registered = {}
-        self.info('loading %s', module)
-        for objname, obj in vars(module).items():
-            if objname.startswith('_'):
-                continue
-            self.load_ancestors_then_object(module.__name__, registered, obj)
-        return registered
+        self._registered = {}
+        if hasattr(module, 'cw_register_objects'):
+            module.cw_register_objects(self)
+        else:
+            self.info('loading %s', module)
+            for objname, obj in vars(module).items():
+                if objname.startswith('_'):
+                    continue
+                self.load_ancestors_then_object(module.__name__, obj)
+        return self._registered
     
-    def load_ancestors_then_object(self, modname, registered, obj):
+    def load_ancestors_then_object(self, modname, obj):
         # skip imported classes
         if getattr(obj, '__module__', None) != modname:
             return
@@ -492,11 +447,11 @@
         except TypeError:
             return
         objname = '%s.%s' % (modname, obj.__name__)
-        if objname in registered:
+        if objname in self._registered:
             return
-        registered[objname] = obj
+        self._registered[objname] = obj
         for parent in obj.__bases__:
-            self.load_ancestors_then_object(modname, registered, parent)
+            self.load_ancestors_then_object(modname, parent)
         self.load_object(obj)
             
     def load_object(self, obj):
@@ -507,6 +462,93 @@
                 raise
             self.exception('vobject %s registration failed: %s', obj, ex)
         
+    # old automatic registration XXX deprecated ###############################
+    
+    def register_vobject_class(self, cls):
+        """handle vobject class registration
+        
+        vobject class with __abstract__ == True in their local dictionnary or
+        with a name starting starting by an underscore are not registered.
+        Also a vobject class needs to have __registry__ and id attributes set
+        to a non empty string to be registered.
+
+        Registration is actually handled by vobject's registerer.
+        """
+        if (cls.__dict__.get('__abstract__') or cls.__name__[0] == '_'
+            or not cls.__registry__ or not cls.id):
+            return
+        regname = cls.__registry__
+        if cls.id in self.config['disable-%s' % regname]:
+            return
+        registry = self._registries.setdefault(regname, {})
+        vobjects = registry.setdefault(cls.id, [])
+        registerer = cls.__registerer__(self, cls)
+        cls = registerer.do_it_yourself(vobjects)
+        if cls:
+            self.register(cls)
+            
+    def unregister_module_vobjects(self, modname):
+        """removes registered objects coming from a given module
+
+        returns a dictionnary classid/class of all classes that will need
+        to be updated after reload (i.e. vobjects referencing classes defined
+        in the <modname> module)
+        """
+        unregistered = {}
+        # browse each registered object
+        for registry, objdict in self.items():
+            for oid, objects in objdict.items():
+                for obj in objects[:]:
+                    objname = obj.classid()
+                    # if the vobject is defined in this module, remove it
+                    if objname.startswith(modname):
+                        unregistered[objname] = obj
+                        objects.remove(obj)
+                        self.debug('unregistering %s in %s registry',
+                                  objname, registry)
+                    # if not, check if the vobject can be found in baseclasses
+                    # (because we also want subclasses to be updated)
+                    else:
+                        if not isinstance(obj, type):
+                            obj = obj.__class__
+                        for baseclass in obj.__bases__:
+                            if hasattr(baseclass, 'classid'):
+                                baseclassid = baseclass.classid()
+                                if baseclassid.startswith(modname):
+                                    unregistered[baseclassid] = baseclass
+                # update oid entry
+                if objects:
+                    objdict[oid] = objects
+                else:
+                    del objdict[oid]
+        return unregistered
+
+    def update_registered_subclasses(self, oldnew_mapping):
+        """updates subclasses of re-registered vobjects
+
+        if baseviews.PrimaryView is changed, baseviews.py will be reloaded
+        automatically and the new version of PrimaryView will be registered.
+        But all existing subclasses must also be notified of this change, and
+        that's what this method does
+
+        :param oldnew_mapping: a dict mapping old version of a class to
+                               the new version
+        """
+        # browse each registered object
+        for objdict in self.values():
+            for objects in objdict.values():
+                for obj in objects:
+                    if not isinstance(obj, type):
+                        obj = obj.__class__
+                    # build new baseclasses tuple
+                    newbases = tuple(oldnew_mapping.get(baseclass, baseclass)
+                                     for baseclass in obj.__bases__)
+                    # update obj's baseclasses tuple (__bases__) if needed
+                    if newbases != obj.__bases__:
+                        self.debug('updating %s.%s base classes',
+                                  obj.__module__, obj.__name__)
+                        obj.__bases__ = newbases
+        
 # init logging 
 set_log_methods(VObject, getLogger('cubicweb'))
 set_log_methods(VRegistry, getLogger('cubicweb.registry'))
--- a/web/action.py	Tue Feb 17 16:25:20 2009 +0100
+++ b/web/action.py	Tue Feb 17 17:28:25 2009 +0100
@@ -6,8 +6,6 @@
 """
 __docformat__ = "restructuredtext en"
 
-from logilab.common.deprecation import class_moved
-
 from cubicweb import role, target
 from cubicweb.selectors import (relation_possible, match_search_state,
                                 one_line_rset, may_add_relation, yes,
--- a/web/application.py	Tue Feb 17 16:25:20 2009 +0100
+++ b/web/application.py	Tue Feb 17 17:28:25 2009 +0100
@@ -18,13 +18,13 @@
 from cubicweb.cwvreg import CubicWebRegistry
 from cubicweb.web import (LOGGER, StatusResponse, DirectResponse, Redirect, NotFound,
                        RemoteCallFailed, ExplicitLogin, InvalidSession)
-from cubicweb.web.component import SingletonComponent
+from cubicweb.web.component import Component
 
 # make session manager available through a global variable so the debug view can
 # print information about web session
 SESSION_MANAGER = None
 
-class AbstractSessionManager(SingletonComponent):
+class AbstractSessionManager(Component):
     """manage session data associated to a session identifier"""
     id = 'sessionmanager'
     
@@ -87,7 +87,7 @@
         raise NotImplementedError()
 
 
-class AbstractAuthenticationManager(SingletonComponent):
+class AbstractAuthenticationManager(Component):
     """authenticate user associated to a request and check session validity"""
     id = 'authmanager'
 
--- a/web/box.py	Tue Feb 17 16:25:20 2009 +0100
+++ b/web/box.py	Tue Feb 17 17:28:25 2009 +0100
@@ -17,8 +17,7 @@
     accepts_registerer, extresources_registerer,
     etype_rtype_priority_registerer)
 #etype_rtype_selector, has_relation,
-from cubicweb.common.view import Template
-from cubicweb.common.appobject import ReloadableMixIn
+from cubicweb.common.view import Template, ReloadableMixIn
 
 from cubicweb.web.htmlwidgets import (BoxLink, BoxWidget, SideBoxWidget,
                                       RawBoxItem, BoxSeparator)
@@ -139,14 +138,6 @@
         return (self.rql, {'x': self.req.user.eid}, 'x')
     
 
-class ExtResourcesBoxTemplate(BoxTemplate):
-    """base class for boxes displaying external resources such as the RSS logo.
-    It should list necessary resources with the .need_resources attribute.
-    """
-    __registerer__ = extresources_registerer
-    need_resources = ()
-
-
 class EntityBoxTemplate(BoxTemplate):
     """base class for boxes related to a single entity"""
     __registerer__ = accepts_registerer
--- a/web/component.py	Tue Feb 17 16:25:20 2009 +0100
+++ b/web/component.py	Tue Feb 17 17:28:25 2009 +0100
@@ -6,10 +6,12 @@
 """
 __docformat__ = "restructuredtext en"
 
+from logilab.common.deprecation import class_moved
+
 from cubicweb.selectors import (
     paginated_rset, one_line_rset, primary_view, match_context_prop,
     condition_compat, accepts_compat, has_relation_compat)
-from cubicweb.common.appobject import Component, SingletonComponent, ComponentMixIn
+from cubicweb.common.appobject import Component
 from cubicweb.common.utils import merge_dicts
 from cubicweb.common.view import View
 from cubicweb.common.registerers import action_registerer
@@ -17,13 +19,7 @@
 
 _ = unicode
 
-class VComponent(ComponentMixIn, View):
-    property_defs = {
-        _('visible'):  dict(type='Boolean', default=True,
-                            help=_('display the box or not')),
-        }    
-
-class EntityVComponent(VComponent):
+class EntityVComponent(Component):
     """abstract base class for additinal components displayed in content
     headers and footer according to:
     
@@ -62,7 +58,7 @@
         raise NotImplementedError()
 
     
-class NavigationComponent(ComponentMixIn, View):
+class NavigationComponent(Component):
     """abstract base class for navigation components"""
     id = 'navigation'
     __selectors__ = (paginated_rset,)
@@ -175,3 +171,9 @@
         self.w(u'<div class="%s">' % self.div_class())
         self.wview(self.vid, rset, title=self.req._(self.title).capitalize())
         self.w(u'</div>')
+
+
+VComponent = class_moved('VComponent', VComponent,
+                         'VComponent is deprecated, use Component')
+SingletonVComponent = class_moved('SingletonVComponent', VComponent,
+                                  'SingletonVComponent is deprecated, use Component and explicit registration control')
--- a/web/request.py	Tue Feb 17 16:25:20 2009 +0100
+++ b/web/request.py	Tue Feb 17 17:28:25 2009 +0100
@@ -1,7 +1,7 @@
 """abstract class for http request
 
 :organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 __docformat__ = "restructuredtext en"
@@ -18,15 +18,15 @@
 
 from logilab.common.decorators import cached
 
-# XXX move _MARKER here once AppObject.external_resource has been removed
 from cubicweb.dbapi import DBAPIRequest
-from cubicweb.common.appobject import _MARKER 
 from cubicweb.common.mail import header
 from cubicweb.common.uilib import remove_html_tags
 from cubicweb.common.utils import SizeConstrainedList, HTMLHead
 from cubicweb.web import (INTERNAL_FIELD_VALUE, LOGGER, NothingToEdit, RequestError,
                        StatusResponse)
 
+_MARKER = object()
+
 
 def list_form_param(form, param, pop=False):
     """get param from form parameters and return its value as a list,
--- a/web/views/actions.py	Tue Feb 17 16:25:20 2009 +0100
+++ b/web/views/actions.py	Tue Feb 17 17:28:25 2009 +0100
@@ -143,7 +143,7 @@
     
 class DeleteAction(Action):
     id = 'delete'
-    __selectors__ = (has_permission('delete'),)
+    __selectors__ = (one_line_rset, has_permission('delete'))
     
     title = _('delete')
     category = 'moreactions' 
@@ -158,7 +158,7 @@
         
 class CopyAction(Action):
     id = 'copy'
-    __selectors__ = (has_permission('add'),)
+    __selectors__ = (one_line_rset, has_permission('add'))
     
     title = _('copy')
     category = 'moreactions'
--- a/web/views/apacherewrite.py	Tue Feb 17 16:25:20 2009 +0100
+++ b/web/views/apacherewrite.py	Tue Feb 17 17:28:25 2009 +0100
@@ -2,7 +2,7 @@
 are much more limited for the moment)
 
 :organization: Logilab
-:copyright: 2007-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2007-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 
@@ -11,7 +11,7 @@
 from re import compile
 
 from cubicweb.web import Redirect
-from cubicweb.web.component import SingletonComponent
+from cubicweb.web.component import Component
 
 class RewriteCond(object):
     def __init__(self, condition, match='host', rules=(), action='rewrite'):
@@ -46,7 +46,7 @@
         return path
 
     
-class ApacheURLRewrite(SingletonComponent):
+class ApacheURLRewrite(Component):
     """inherit from this class with actual rules to activate apache style rewriting
 
     rules should have the form :
--- a/web/views/basecomponents.py	Tue Feb 17 16:25:20 2009 +0100
+++ b/web/views/basecomponents.py	Tue Feb 17 17:28:25 2009 +0100
@@ -20,13 +20,13 @@
                                     match_form_params)
 
 from cubicweb.web.htmlwidgets import MenuWidget, PopupBoxMenu, BoxSeparator, BoxLink
-from cubicweb.web.component import (VComponent, EntityVComponent, 
+from cubicweb.web.component import (Component, EntityVComponent, 
                                     RelatedObjectsVComponent)
 
 _ = unicode
 
 
-class RQLInputForm(VComponent):
+class RQLInputForm(Component):
     """build the rql input form, usually displayed in the header"""
     id = 'rqlinput'
     visible = False
@@ -55,7 +55,7 @@
         self.w(u'</form></div>')
 
 
-class ApplLogo(VComponent):
+class ApplLogo(Component):
     """build the application logo, usually displayed in the header"""
     id = 'logo'
     site_wide = True # don't want user to hide this component using an eproperty
@@ -64,7 +64,7 @@
                % (self.req.base_url(), self.req.external_resource('LOGO')))
 
 
-class ApplHelp(VComponent):
+class ApplHelp(Component):
     """build the help button, usually displayed in the header"""
     id = 'help'
     def call(self):
@@ -73,7 +73,7 @@
                   self.req._(u'help'),))
 
 
-class UserLink(VComponent):
+class UserLink(Component):
     """if the user is the anonymous user, build a link to login
     else a link to the connected user object with a loggout link
     """
@@ -114,7 +114,7 @@
                    % (self.build_url('login'), self.req._('login')))
 
 
-class ApplicationMessage(VComponent):
+class ApplicationMessage(Component):
     """display application's messages given using the __message parameter
     into a special div section
     """
@@ -169,7 +169,7 @@
                        displaycols=displaycols, headers=headers)
 
 
-class ApplicationName(VComponent):
+class ApplicationName(Component):
     """display the application name"""
     id = 'appliname'
 
@@ -190,7 +190,7 @@
     help = _('contentnavigation_seealso_description')
 
     
-class EtypeRestrictionComponent(VComponent):
+class EtypeRestrictionComponent(Component):
     """displays the list of entity types contained in the resultset
     to be able to filter accordingly.
     """
@@ -240,14 +240,14 @@
         
 
 
-class RSSFeedURL(VComponent):
+class RSSFeedURL(Component):
     id = 'rss_feed_url'
     __selectors__ = (non_final_entity(),)
     
     def feed_url(self):
         return self.build_url(rql=self.limited_rql(), vid='rss')
 
-class RSSEntityFeedURL(VComponent):
+class RSSEntityFeedURL(Component):
     id = 'rss_feed_url'
     __selectors__ = (non_final_entity(), one_line_rset)
     
--- a/web/views/boxes.py	Tue Feb 17 16:25:20 2009 +0100
+++ b/web/views/boxes.py	Tue Feb 17 17:28:25 2009 +0100
@@ -19,7 +19,7 @@
 
 from cubicweb.selectors import any_rset, appobject_selectable, match_user_groups
 from cubicweb.web.htmlwidgets import BoxWidget, BoxMenu, BoxHtml, RawBoxItem
-from cubicweb.web.box import BoxTemplate, ExtResourcesBoxTemplate
+from cubicweb.web.box import BoxTemplate
 
 _ = unicode
 
@@ -136,7 +136,6 @@
     visible = True # enabled by default
     title = _('search')
     order = 0
-    need_resources = 'SEARCH_GO'
     formdef = u"""<form action="%s">
 <table id="tsearch"><tr><td>
 <input id="norql" type="text" accesskey="q" tabindex="%s" title="search text" value="%s" name="rql" />
@@ -186,19 +185,22 @@
             box.render(self.w)
 
         
-class RSSIconBox(ExtResourcesBoxTemplate):
+class RSSIconBox(BoxTemplate):
     """just display the RSS icon on uniform result set"""
-    __selectors__ = ExtResourcesBoxTemplate.__selectors__ + (appobject_selectable('components', 'rss_feed_url'),)
+    id = 'rss'
+    __selectors__ = BoxTemplate.__selectors__ + (appobject_selectable('components', 'rss_feed_url'),)
     
-    id = 'rss'
     order = 999
-    need_resources = 'RSS_LOGO',
     visible = False
     
     def call(self, **kwargs):
+        try:
+            rss = self.req.external_resource('RSS_LOGO')
+        except KeyError:
+            self.error('missing RSS_LOGO external resource')
+            return
         urlgetter = self.vreg.select_component('rss_feed_url', self.req, self.rset)
         url = urlgetter.feed_url()
-        rss = self.req.external_resource('RSS_LOGO')
         self.w(u'<a href="%s"><img src="%s" alt="rss"/></a>\n' % (html_escape(url), rss))
 
 
--- a/web/views/facets.py	Tue Feb 17 16:25:20 2009 +0100
+++ b/web/views/facets.py	Tue Feb 17 17:28:25 2009 +0100
@@ -27,8 +27,7 @@
 class FilterBox(BoxTemplate):
     """filter results of a query"""
     id = 'filter_box'
-    __selectors__ = (chainfirst(contextview_selector,
-                                chainall(non_final_entity, two_lines_rset)),
+    __selectors__ = ((non_final_entity() & two_lines_rset) | contextview_selector,
                      match_context_prop)
     context = 'left'
     title = _('boxes_filter_box')
--- a/web/views/magicsearch.py	Tue Feb 17 16:25:20 2009 +0100
+++ b/web/views/magicsearch.py	Tue Feb 17 17:28:25 2009 +0100
@@ -15,7 +15,7 @@
 from rql.nodes import Relation
 
 from cubicweb import Unauthorized
-from cubicweb.common.appobject import Component, SingletonComponent
+from cubicweb.common.appobject import Component
 
 LOGGER = getLogger('cubicweb.magicsearch')
 
@@ -349,7 +349,7 @@
 
 
 
-class MagicSearchComponent(SingletonComponent):
+class MagicSearchComponent(Component):
     id  = 'magicsearch'
     def __init__(self, req, rset=None):
         super(MagicSearchComponent, self).__init__(req, rset)
--- a/web/views/tableview.py	Tue Feb 17 16:25:20 2009 +0100
+++ b/web/views/tableview.py	Tue Feb 17 17:28:25 2009 +0100
@@ -5,6 +5,8 @@
 :copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
+from __future__ import with_statement
+
 __docformat__ = "restructuredtext en"
 
 from simplejson import dumps
@@ -222,7 +224,9 @@
         
 
     def render(self, cellvid, row, col, w):
-        self.view('cell', self.rset, row=row, col=col, cellvid=cellvid, w=w)
+        from cubicweb.selectors import traced_selection
+        with traced_selection( ('cell',) ):
+            self.view('cell', self.rset, row=row, col=col, cellvid=cellvid, w=w)
         
     def get_rows(self):
         return self.rset
--- a/web/views/urlpublishing.py	Tue Feb 17 16:25:20 2009 +0100
+++ b/web/views/urlpublishing.py	Tue Feb 17 17:28:25 2009 +0100
@@ -18,7 +18,7 @@
 because of redirecting instead of direct traversal
 
 :organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 
@@ -28,7 +28,7 @@
 
 from cubicweb import RegistryException, typed_eid
 from cubicweb.web import NotFound, Redirect
-from cubicweb.web.component import SingletonComponent, Component
+from cubicweb.web.component import Component, Component
 
 
 class PathDontMatch(Exception):
@@ -36,7 +36,7 @@
     a path
     """
     
-class URLPublisherComponent(SingletonComponent):
+class URLPublisherComponent(Component):
     """associate url's path to view identifier / rql queries,
     by applying a chain of urlpathevaluator components.