remove 3.9 bw compat
authorAurelien Campeas <aurelien.campeas@logilab.fr>
Mon, 26 Aug 2013 16:14:09 +0200
changeset 9256 697a8181ba30
parent 9255 46f41c3e1443
child 9257 ce338133c92c
remove 3.9 bw compat In cw 3.9, interfaces are deprecated and replaced with adapters, yielding a lot of bw compat in many places -- most if this patch is concerned with the interface bw compat - cwvreg: interface cleanup - doc/adapters.rst: interface cleanup - entities/adapters.py, wfobjs.py: interfaces bw compat - entity.py: interfaces bw compat, also get_value, delete, attr_metadata, has_perm, set_related_cache, clear_related_cache, clear_related_cache, related_rql - predicates.py: score_interfaces & implements - interfaces.py & mixins.py: 100% gone - view.py: implement_adapter_compat, unwrap_adapter_compat - calendar.py, editcontroller.py, ibreadcrumbs.py, navigation.py, xmlrss.py: interface bw compat - treeview.py: salvage one function from mixins.py Related to #2782004.
cwvreg.py
doc/3.18.rst
doc/book/en/devrepo/entityclasses/adapters.rst
doc/book/en/tutorials/advanced/part04_ui-base.rst
doc/book/en/tutorials/advanced/part05_ui-advanced.rst
entities/adapters.py
entities/test/unittest_base.py
entities/wfobjs.py
entity.py
interfaces.py
mixins.py
predicates.py
view.py
web/views/calendar.py
web/views/editcontroller.py
web/views/ibreadcrumbs.py
web/views/navigation.py
web/views/treeview.py
web/views/xmlrss.py
--- a/cwvreg.py	Mon Aug 26 16:12:30 2013 +0200
+++ b/cwvreg.py	Mon Aug 26 16:14:09 2013 +0200
@@ -211,8 +211,7 @@
 
 from cubicweb import (CW_SOFTWARE_ROOT, ETYPE_NAME_MAP, CW_EVENT_MANAGER,
                       onevent, Binary, UnknownProperty, UnknownEid)
-from cubicweb.predicates import (implements, appobject_selectable,
-                                 _reset_is_instance_cache)
+from cubicweb.predicates import appobject_selectable, _reset_is_instance_cache
 
 
 @onevent('before-registry-reload')
@@ -230,15 +229,6 @@
     sys.modules.pop('cubicweb.web.uicfg', None)
     sys.modules.pop('cubicweb.web.uihelper', None)
 
-def use_interfaces(obj):
-    """return interfaces required by the given object by searching for
-    `implements` predicate
-    """
-    impl = obj.__select__.search_selector(implements)
-    if impl:
-        return sorted(impl.expected_ifaces)
-    return ()
-
 def require_appobject(obj):
     """return appobjects required by the given object by searching for
     `appobject_selectable` predicate
@@ -568,7 +558,6 @@
     def reset(self):
         CW_EVENT_MANAGER.emit('before-registry-reset', self)
         super(CWRegistryStore, self).reset()
-        self._needs_iface = {}
         self._needs_appobject = {}
         # two special registries, propertydefs which care all the property
         # definitions, and propertyvals which contains values for those
@@ -641,20 +630,6 @@
                 for obj in objects:
                     obj.schema = schema
 
-    @deprecated('[3.9] use .register instead')
-    def register_if_interface_found(self, obj, ifaces, **kwargs):
-        """register `obj` but remove it if no entity class implements one of
-        the given `ifaces` interfaces at the end of the registration process.
-
-        Extra keyword arguments are given to the
-        :meth:`~cubicweb.cwvreg.CWRegistryStore.register` function.
-        """
-        self.register(obj, **kwargs)
-        if not isinstance(ifaces,  (tuple, list)):
-            self._needs_iface[obj] = (ifaces,)
-        else:
-            self._needs_iface[obj] = ifaces
-
     def register(self, obj, *args, **kwargs):
         """register `obj` application object into `registryname` or
         `obj.__registry__` if not specified, with identifier `oid` or
@@ -665,15 +640,6 @@
         """
         obj = related_appobject(obj)
         super(CWRegistryStore, self).register(obj, *args, **kwargs)
-        # XXX bw compat
-        ifaces = use_interfaces(obj)
-        if ifaces:
-            if not obj.__name__.endswith('Adapter') and \
-                   any(iface for iface in ifaces if not isinstance(iface, basestring)):
-                warn('[3.9] %s: interfaces in implements selector are '
-                     'deprecated in favor of adapters / adaptable '
-                     'selector' % obj.__name__, DeprecationWarning)
-            self._needs_iface[obj] = ifaces
         depends_on = require_appobject(obj)
         if depends_on is not None:
             self._needs_appobject[obj] = depends_on
@@ -694,34 +660,7 @@
         # we may want to keep interface dependent objects (e.g.for i18n
         # catalog generation)
         if self.config.cleanup_interface_sobjects:
-            # XXX deprecated with cw 3.9: remove appobjects that don't support
-            # any available interface
-            implemented_interfaces = set()
-            if 'Any' in self.get('etypes', ()):
-                for etype in self.schema.entities():
-                    if etype.final:
-                        continue
-                    cls = self['etypes'].etype_class(etype)
-                    if cls.__implements__:
-                        warn('[3.9] %s: using __implements__/interfaces are '
-                             'deprecated in favor of adapters' % cls.__name__,
-                             DeprecationWarning)
-                    for iface in cls.__implements__:
-                        implemented_interfaces.update(iface.__mro__)
-                    implemented_interfaces.update(cls.__mro__)
-            for obj, ifaces in self._needs_iface.items():
-                ifaces = frozenset(isinstance(iface, basestring)
-                                   and iface in self.schema
-                                   and self['etypes'].etype_class(iface)
-                                   or iface
-                                   for iface in ifaces)
-                if not ('Any' in ifaces or ifaces & implemented_interfaces):
-                    reg = self[obj_registries(obj)[0]]
-                    self.debug('unregister %s (no implemented '
-                               'interface among %s)', reg.objid(obj), ifaces)
-                    self.unregister(obj)
-            # since 3.9: remove appobjects which depending on other, unexistant
-            # appobjects
+            # remove appobjects which depend on other, unexistant appobjects
             for obj, (regname, regids) in self._needs_appobject.items():
                 try:
                     registry = self[regname]
--- a/doc/3.18.rst	Mon Aug 26 16:12:30 2013 +0200
+++ b/doc/3.18.rst	Mon Aug 26 16:14:09 2013 +0200
@@ -28,3 +28,6 @@
   (see `#2936496 <http://www.cubicweb.org/2936496>`_)
 
 * all 3.8 backward compat is gone
+
+* all 3.9 backward compat for the interface -> adapter transition is
+  gone
--- a/doc/book/en/devrepo/entityclasses/adapters.rst	Mon Aug 26 16:12:30 2013 +0200
+++ b/doc/book/en/devrepo/entityclasses/adapters.rst	Mon Aug 26 16:14:09 2013 +0200
@@ -10,13 +10,7 @@
 .. _`interfaces`: http://java.sun.com/docs/books/tutorial/java/concepts/interface.html
 .. _`adapter`: http://en.wikipedia.org/wiki/Adapter_pattern
 
-In |cubicweb| adapters provide logical functionalities to entity types. They
-are introduced in version `3.9`. Before that one had to implement Interfaces in
-entity classes to achieve a similar goal. However, the problem with this
-approach is that is clutters the entity class's namespace, exposing name
-collision risks with schema attributes/relations or even methods names
-(different interfaces may define the same method with not necessarily the same
-behaviour expected).
+In |cubicweb| adapters provide logical functionalities to entity types.
 
 Definition of an adapter is quite trivial. An excerpt from cubicweb
 itself (found in :mod:`cubicweb.entities.adapters`):
--- a/doc/book/en/tutorials/advanced/part04_ui-base.rst	Mon Aug 26 16:12:30 2013 +0200
+++ b/doc/book/en/tutorials/advanced/part04_ui-base.rst	Mon Aug 26 16:14:09 2013 +0200
@@ -194,8 +194,6 @@
 
 .. Note::
 
-    * Adapters have been introduced in CubicWeb 3.9 / cubicweb-folder 1.8.
-
     * As seen earlier, we want to **replace** the folder's `ITree` adapter by our
       implementation, hence the custom `registration_callback` method.
 
@@ -241,12 +239,6 @@
 ascendant/descendant ordering and a strict comparison with current file's name
 (the "X" variable representing the current file).
 
-.. Note::
-
-    * Former `implements` selector should be replaced by one of `is_instance` /
-      `adaptable` selector with CubicWeb >= 3.9. In our case, `is_instance` to
-      tell our adapter is able to adapt `File` entities.
-
 Notice that this query supposes we wont have two files of the same name in the
 same folder, else things may go wrong. Fixing this is out of the scope of this
 blog. And as I would like to have at some point a smarter, context sensitive
@@ -358,7 +350,7 @@
 You'll have to answer some questions, as we've seen in `an earlier post`_.
 
 Now that everything is tested, I can transfer the new code to the production
-server, `apt-get upgrade` cubicweb 3.9 and its dependencies, and eventually
+server, `apt-get upgrade` cubicweb and its dependencies, and eventually
 upgrade the production instance.
 
 
--- a/doc/book/en/tutorials/advanced/part05_ui-advanced.rst	Mon Aug 26 16:12:30 2013 +0200
+++ b/doc/book/en/tutorials/advanced/part05_ui-advanced.rst	Mon Aug 26 16:14:09 2013 +0200
@@ -1,8 +1,6 @@
 Building my photos web site with |cubicweb| part V: let's make it even more user friendly
 =========================================================================================
 
-We'll now see how to benefit from features introduced in 3.9 and 3.10 releases of CubicWeb
-
 .. _uiprops:
 
 Step 1: tired of the default look?
@@ -29,9 +27,9 @@
 
    LOGO = data('logo.jpg')
 
-The uiprops machinery has been introduced in `CubicWeb 3.9`_. It is used to define
-some static file resources, such as the logo, default Javascript / CSS files, as
-well as CSS properties (we'll see that later).
+The uiprops machinery is used to define some static file resources,
+such as the logo, default Javascript / CSS files, as well as CSS
+properties (we'll see that later).
 
 .. Note::
    This file is imported specifically by |cubicweb|, with a predefined name space,
@@ -373,5 +371,4 @@
 
 
 .. _`CubicWeb 3.10`: http://www.cubicweb.org/blogentry/1330518
-.. _`CubicWeb 3.9`: http://www.cubicweb.org/blogentry/1179899
 .. _`here`: http://webdesign.about.com/od/css3/f/blfaqbgsize.htm
--- a/entities/adapters.py	Mon Aug 26 16:12:30 2013 +0200
+++ b/entities/adapters.py	Mon Aug 26 16:14:09 2013 +0200
@@ -28,9 +28,7 @@
 from logilab.common.decorators import cached
 
 from cubicweb import ValidationError, view
-from cubicweb.predicates import (implements, is_instance, relation_possible,
-                                match_exception)
-from cubicweb.interfaces import IDownloadable, ITree
+from cubicweb.predicates import is_instance, relation_possible, match_exception
 
 
 class IEmailableAdapter(view.EntityAdapter):
@@ -67,11 +65,9 @@
 
 
 class INotifiableAdapter(view.EntityAdapter):
-    __needs_bw_compat__ = True
     __regid__ = 'INotifiable'
     __select__ = is_instance('Any')
 
-    @view.implements_adapter_compat('INotifiableAdapter')
     def notification_references(self, view):
         """used to control References field of email send on notification
         for this entity. `view` is the notification view.
@@ -167,27 +163,25 @@
 
 class IDownloadableAdapter(view.EntityAdapter):
     """interface for downloadable entities"""
-    __needs_bw_compat__ = True
     __regid__ = 'IDownloadable'
-    __select__ = implements(IDownloadable, warn=False) # XXX for bw compat, else should be abstract
+    __abstract__ = True
 
-    @view.implements_adapter_compat('IDownloadable')
     def download_url(self, **kwargs): # XXX not really part of this interface
         """return an url to download entity's content"""
         raise NotImplementedError
-    @view.implements_adapter_compat('IDownloadable')
+
     def download_content_type(self):
         """return MIME type of the downloadable content"""
         raise NotImplementedError
-    @view.implements_adapter_compat('IDownloadable')
+
     def download_encoding(self):
         """return encoding of the downloadable content"""
         raise NotImplementedError
-    @view.implements_adapter_compat('IDownloadable')
+
     def download_file_name(self):
         """return file name of the downloadable content"""
         raise NotImplementedError
-    @view.implements_adapter_compat('IDownloadable')
+
     def download_data(self):
         """return actual data of the downloadable content"""
         raise NotImplementedError
@@ -219,27 +213,16 @@
     .. automethod: children_rql
     .. automethod: path
     """
-    __needs_bw_compat__ = True
     __regid__ = 'ITree'
-    __select__ = implements(ITree, warn=False) # XXX for bw compat, else should be abstract
+    __abstract__ = True
 
     child_role = 'subject'
     parent_role = 'object'
 
-    @property
-    def tree_relation(self):
-        warn('[3.9] tree_attribute is deprecated, define tree_relation on a custom '
-             'ITree for %s instead' % (self.entity.__class__),
-             DeprecationWarning)
-        return self.entity.tree_attribute
-
-    # XXX should be removed from the public interface
-    @view.implements_adapter_compat('ITree')
     def children_rql(self):
         """Returns RQL to get the children of the entity."""
         return self.entity.cw_related_rql(self.tree_relation, self.parent_role)
 
-    @view.implements_adapter_compat('ITree')
     def different_type_children(self, entities=True):
         """Return children entities of different type as this entity.
 
@@ -253,7 +236,6 @@
             return [e for e in res if e.e_schema != eschema]
         return res.filtered_rset(lambda x: x.e_schema != eschema, self.entity.cw_col)
 
-    @view.implements_adapter_compat('ITree')
     def same_type_children(self, entities=True):
         """Return children entities of the same type as this entity.
 
@@ -267,23 +249,19 @@
             return [e for e in res if e.e_schema == eschema]
         return res.filtered_rset(lambda x: x.e_schema is eschema, self.entity.cw_col)
 
-    @view.implements_adapter_compat('ITree')
     def is_leaf(self):
         """Returns True if the entity does not have any children."""
         return len(self.children()) == 0
 
-    @view.implements_adapter_compat('ITree')
     def is_root(self):
         """Returns true if the entity is root of the tree (e.g. has no parent).
         """
         return self.parent() is None
 
-    @view.implements_adapter_compat('ITree')
     def root(self):
         """Return the root entity of the tree."""
         return self._cw.entity_from_eid(self.path()[0])
 
-    @view.implements_adapter_compat('ITree')
     def parent(self):
         """Returns the parent entity if any, else None (e.g. if we are on the
         root).
@@ -294,7 +272,6 @@
         except (KeyError, IndexError):
             return None
 
-    @view.implements_adapter_compat('ITree')
     def children(self, entities=True, sametype=False):
         """Return children entities.
 
@@ -307,7 +284,6 @@
             return self.entity.related(self.tree_relation, self.parent_role,
                                        entities=entities)
 
-    @view.implements_adapter_compat('ITree')
     def iterparents(self, strict=True):
         """Return an iterator on the parents of the entity."""
         def _uptoroot(self):
@@ -322,7 +298,6 @@
             return chain([self.entity], _uptoroot(self))
         return _uptoroot(self)
 
-    @view.implements_adapter_compat('ITree')
     def iterchildren(self, _done=None):
         """Return an iterator over the item's children."""
         if _done is None:
@@ -334,7 +309,6 @@
             yield child
             _done.add(child.eid)
 
-    @view.implements_adapter_compat('ITree')
     def prefixiter(self, _done=None):
         """Return an iterator over the item's descendants in a prefixed order."""
         if _done is None:
@@ -347,7 +321,6 @@
             for entity in child.cw_adapt_to('ITree').prefixiter(_done):
                 yield entity
 
-    @view.implements_adapter_compat('ITree')
     @cached
     def path(self):
         """Returns the list of eids from the root object to this object."""
--- a/entities/test/unittest_base.py	Mon Aug 26 16:12:30 2013 +0200
+++ b/entities/test/unittest_base.py	Mon Aug 26 16:14:09 2013 +0200
@@ -25,7 +25,6 @@
 
 from cubicweb.devtools.testlib import CubicWebTC
 
-from cubicweb.interfaces import IMileStone, ICalendarable
 from cubicweb.entities import AnyEntity
 
 
@@ -134,27 +133,6 @@
         self.request().create_entity('CWGroup', name=u'logilab', reverse_in_group=e)
 
 
-class InterfaceTC(CubicWebTC):
-
-    def test_nonregr_subclasses_and_mixins_interfaces(self):
-        from cubicweb.entities.wfobjs import WorkflowableMixIn
-        WorkflowableMixIn.__implements__ = (ICalendarable,)
-        CWUser = self.vreg['etypes'].etype_class('CWUser')
-        class MyUser(CWUser):
-            __implements__ = (IMileStone,)
-        self.vreg._loadedmods[__name__] = {}
-        self.vreg.register(MyUser)
-        self.vreg['etypes'].initialization_completed()
-        MyUser_ = self.vreg['etypes'].etype_class('CWUser')
-        # a copy is done systematically
-        self.assertTrue(issubclass(MyUser_, MyUser))
-        self.assertTrue(implements(MyUser_, IMileStone))
-        self.assertTrue(implements(MyUser_, ICalendarable))
-        # original class should not have beed modified, only the copy
-        self.assertTrue(implements(MyUser, IMileStone))
-        self.assertFalse(implements(MyUser, ICalendarable))
-
-
 class SpecializedEntityClassesTC(CubicWebTC):
 
     def select_eclass(self, etype):
--- a/entities/wfobjs.py	Mon Aug 26 16:12:30 2013 +0200
+++ b/entities/wfobjs.py	Mon Aug 26 16:14:09 2013 +0200
@@ -32,7 +32,6 @@
 from cubicweb.entities import AnyEntity, fetch_config
 from cubicweb.view import EntityAdapter
 from cubicweb.predicates import relation_possible
-from cubicweb.mixins import MI_REL_TRIGGERS
 
 class WorkflowException(Exception): pass
 
@@ -379,65 +378,8 @@
         return self.by_transition and self.by_transition[0] or None
 
 
-class WorkflowableMixIn(object):
-    """base mixin providing workflow helper methods for workflowable entities.
-    This mixin will be automatically set on class supporting the 'in_state'
-    relation (which implies supporting 'wf_info_for' as well)
-    """
 
-    @property
-    @deprecated("[3.9] use entity.cw_adapt_to('IWorkflowable').main_workflow")
-    def main_workflow(self):
-        return self.cw_adapt_to('IWorkflowable').main_workflow
-    @property
-    @deprecated("[3.9] use entity.cw_adapt_to('IWorkflowable').current_workflow")
-    def current_workflow(self):
-        return self.cw_adapt_to('IWorkflowable').current_workflow
-    @property
-    @deprecated("[3.9] use entity.cw_adapt_to('IWorkflowable').current_state")
-    def current_state(self):
-        return self.cw_adapt_to('IWorkflowable').current_state
-    @property
-    @deprecated("[3.9] use entity.cw_adapt_to('IWorkflowable').state")
-    def state(self):
-        return self.cw_adapt_to('IWorkflowable').state
-    @property
-    @deprecated("[3.9] use entity.cw_adapt_to('IWorkflowable').printable_state")
-    def printable_state(self):
-        return self.cw_adapt_to('IWorkflowable').printable_state
-    @property
-    @deprecated("[3.9] use entity.cw_adapt_to('IWorkflowable').workflow_history")
-    def workflow_history(self):
-        return self.cw_adapt_to('IWorkflowable').workflow_history
-
-    @deprecated("[3.9] use entity.cw_adapt_to('IWorkflowable').cwetype_workflow()")
-    def cwetype_workflow(self):
-        return self.cw_adapt_to('IWorkflowable').main_workflow()
-    @deprecated("[3.9] use entity.cw_adapt_to('IWorkflowable').latest_trinfo()")
-    def latest_trinfo(self):
-        return self.cw_adapt_to('IWorkflowable').latest_trinfo()
-    @deprecated("[3.9] use entity.cw_adapt_to('IWorkflowable').possible_transitions()")
-    def possible_transitions(self, type='normal'):
-        return self.cw_adapt_to('IWorkflowable').possible_transitions(type)
-    @deprecated("[3.9] use entity.cw_adapt_to('IWorkflowable').fire_transition()")
-    def fire_transition(self, tr, comment=None, commentformat=None):
-        return self.cw_adapt_to('IWorkflowable').fire_transition(tr, comment, commentformat)
-    @deprecated("[3.9] use entity.cw_adapt_to('IWorkflowable').change_state()")
-    def change_state(self, statename, comment=None, commentformat=None, tr=None):
-        return self.cw_adapt_to('IWorkflowable').change_state(statename, comment, commentformat, tr)
-    @deprecated("[3.9] use entity.cw_adapt_to('IWorkflowable').subworkflow_input_trinfo()")
-    def subworkflow_input_trinfo(self):
-        return self.cw_adapt_to('IWorkflowable').subworkflow_input_trinfo()
-    @deprecated("[3.9] use entity.cw_adapt_to('IWorkflowable').subworkflow_input_transition()")
-    def subworkflow_input_transition(self):
-        return self.cw_adapt_to('IWorkflowable').subworkflow_input_transition()
-
-
-MI_REL_TRIGGERS[('in_state', 'subject')] = WorkflowableMixIn
-
-
-
-class IWorkflowableAdapter(WorkflowableMixIn, EntityAdapter):
+class IWorkflowableAdapter(EntityAdapter):
     """base adapter providing workflow helper methods for workflowable entities.
     """
     __regid__ = 'IWorkflowable'
--- a/entity.py	Mon Aug 26 16:12:30 2013 +0200
+++ b/entity.py	Mon Aug 26 16:14:09 2013 +0200
@@ -42,7 +42,6 @@
 from cubicweb.rqlrewrite import RQLRewriter
 
 from cubicweb.uilib import soup2xhtml
-from cubicweb.mixins import MI_REL_TRIGGERS
 from cubicweb.mttransforms import ENGINE
 
 _marker = object()
@@ -194,31 +193,11 @@
             setattr(cls, rschema.type, Attribute(rschema.type))
         mixins = []
         for rschema, _, role in eschema.relation_definitions():
-            if (rschema, role) in MI_REL_TRIGGERS:
-                mixin = MI_REL_TRIGGERS[(rschema, role)]
-                if not (issubclass(cls, mixin) or mixin in mixins): # already mixed ?
-                    mixins.append(mixin)
-                for iface in getattr(mixin, '__implements__', ()):
-                    if not interface.implements(cls, iface):
-                        interface.extend(cls, iface)
             if role == 'subject':
                 attr = rschema.type
             else:
                 attr = 'reverse_%s' % rschema.type
             setattr(cls, attr, Relation(rschema, role))
-        if mixins:
-            # see etype class instantation in cwvreg.ETypeRegistry.etype_class method:
-            # due to class dumping, cls is the generated top level class with actual
-            # user class as (only) parent. Since we want to be able to override mixins
-            # method from this user class, we have to take care to insert mixins after that
-            # class
-            #
-            # note that we don't plug mixins as user class parent since it causes pb
-            # with some cases of entity classes inheritance.
-            mixins.insert(0, cls.__bases__[0])
-            mixins += cls.__bases__[1:]
-            cls.__bases__ = tuple(mixins)
-            cls.info('plugged %s mixins on %s', mixins, cls)
 
     fetch_attrs = ('modification_date',)
 
@@ -1338,34 +1317,6 @@
     def clear_all_caches(self):
         return self.cw_clear_all_caches()
 
-    @deprecated('[3.9] use entity.cw_attr_value(attr)')
-    def get_value(self, name):
-        return self.cw_attr_value(name)
-
-    @deprecated('[3.9] use entity.cw_delete()')
-    def delete(self, **kwargs):
-        return self.cw_delete(**kwargs)
-
-    @deprecated('[3.9] use entity.cw_attr_metadata(attr, metadata)')
-    def attr_metadata(self, attr, metadata):
-        return self.cw_attr_metadata(attr, metadata)
-
-    @deprecated('[3.9] use entity.cw_has_perm(action)')
-    def has_perm(self, action):
-        return self.cw_has_perm(action)
-
-    @deprecated('[3.9] use entity.cw_set_relation_cache(rtype, role, rset)')
-    def set_related_cache(self, rtype, role, rset):
-        self.cw_set_relation_cache(rtype, role, rset)
-
-    @deprecated('[3.9] use entity.cw_clear_relation_cache(rtype, role)')
-    def clear_related_cache(self, rtype=None, role=None):
-        self.cw_clear_relation_cache(rtype, role)
-
-    @deprecated('[3.9] use entity.cw_related_rql(rtype, [role, [targettypes]])')
-    def related_rql(self, rtype, role='subject', targettypes=None):
-        return self.cw_related_rql(rtype, role, targettypes)
-
     @property
     @deprecated('[3.10] use entity.cw_edited')
     def edited_attributes(self):
--- a/interfaces.py	Mon Aug 26 16:12:30 2013 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,214 +0,0 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
-#
-# This file is part of CubicWeb.
-#
-# CubicWeb is free software: you can redistribute it and/or modify it under the
-# terms of the GNU Lesser General Public License as published by the Free
-# Software Foundation, either version 2.1 of the License, or (at your option)
-# any later version.
-#
-# CubicWeb is distributed in the hope that it will be useful, but WITHOUT
-# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
-# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
-# details.
-#
-# You should have received a copy of the GNU Lesser General Public License along
-# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
-"""Standard interfaces. Deprecated in favor of adapters.
-
-.. note::
-
-  The `implements` selector used to match not only entity classes but also their
-  interfaces. This will disappear in a future version. You should define an
-  adapter for that interface and use `adaptable('MyIFace')` selector on appobjects
-  that require that interface.
-
-"""
-__docformat__ = "restructuredtext en"
-
-from logilab.common.interface import Interface
-
-
-# XXX deprecates in favor of IProgressAdapter
-class IProgress(Interface):
-    """something that has a cost, a state and a progression"""
-
-    @property
-    def cost(self):
-        """the total cost"""
-
-    @property
-    def done(self):
-        """what is already done"""
-
-    @property
-    def todo(self):
-        """what remains to be done"""
-
-    def progress_info(self):
-        """returns a dictionary describing progress/estimated cost of the
-        version.
-
-        - mandatory keys are (''estimated', 'done', 'todo')
-
-        - optional keys are ('notestimated', 'notestimatedcorrected',
-          'estimatedcorrected')
-
-        'noestimated' and 'notestimatedcorrected' should default to 0
-        'estimatedcorrected' should default to 'estimated'
-        """
-
-    def finished(self):
-        """returns True if status is finished"""
-
-    def in_progress(self):
-        """returns True if status is not finished"""
-
-    def progress(self):
-        """returns the % progress of the task item"""
-
-# XXX deprecates in favor of IMileStoneAdapter
-class IMileStone(IProgress):
-    """represents an ITask's item"""
-
-    parent_type = None # specify main task's type
-
-    def get_main_task(self):
-        """returns the main ITask entity"""
-
-    def initial_prevision_date(self):
-        """returns the initial expected end of the milestone"""
-
-    def eta_date(self):
-        """returns expected date of completion based on what remains
-        to be done
-        """
-
-    def completion_date(self):
-        """returns date on which the subtask has been completed"""
-
-    def contractors(self):
-        """returns the list of persons supposed to work on this task"""
-
-# XXX deprecates in favor of IEmbedableAdapter
-class IEmbedable(Interface):
-    """interface for embedable entities"""
-
-    def embeded_url(self):
-        """embed action interface"""
-
-# XXX deprecates in favor of ICalendarViewsAdapter
-class ICalendarViews(Interface):
-    """calendar views interface"""
-    def matching_dates(self, begin, end):
-        """
-        :param begin: day considered as begin of the range (`DateTime`)
-        :param end: day considered as end of the range (`DateTime`)
-
-        :return:
-          a list of dates (`DateTime`) in the range [`begin`, `end`] on which
-          this entity apply
-        """
-
-# XXX deprecates in favor of ICalendarableAdapter
-class ICalendarable(Interface):
-    """interface for items that do have a begin date 'start' and an end date 'stop'
-    """
-
-    @property
-    def start(self):
-        """return start date"""
-
-    @property
-    def stop(self):
-        """return stop state"""
-
-# XXX deprecates in favor of ICalendarableAdapter
-class ITimetableViews(Interface):
-    """timetable views interface"""
-    def timetable_date(self):
-        """XXX explain
-
-        :return: date (`DateTime`)
-        """
-
-# XXX deprecates in favor of IGeocodableAdapter
-class IGeocodable(Interface):
-    """interface required by geocoding views such as gmap-view"""
-
-    @property
-    def latitude(self):
-        """returns the latitude of the entity"""
-
-    @property
-    def longitude(self):
-        """returns the longitude of the entity"""
-
-    def marker_icon(self):
-        """returns the icon that should be used as the marker"""
-
-
-# XXX deprecates in favor of IEmailableAdapter
-class IFeed(Interface):
-    """interface for entities with rss flux"""
-
-    def rss_feed_url(self):
-        """"""
-
-# XXX deprecates in favor of IDownloadableAdapter
-class IDownloadable(Interface):
-    """interface for downloadable entities"""
-
-    def download_url(self): # XXX not really part of this interface
-        """return an url to download entity's content"""
-    def download_content_type(self):
-        """return MIME type of the downloadable content"""
-    def download_encoding(self):
-        """return encoding of the downloadable content"""
-    def download_file_name(self):
-        """return file name of the downloadable content"""
-    def download_data(self):
-        """return actual data of the downloadable content"""
-
-# XXX deprecates in favor of IPrevNextAdapter
-class IPrevNext(Interface):
-    """interface for entities which can be linked to a previous and/or next
-    entity
-    """
-
-    def next_entity(self):
-        """return the 'next' entity"""
-    def previous_entity(self):
-        """return the 'previous' entity"""
-
-# XXX deprecates in favor of IBreadCrumbsAdapter
-class IBreadCrumbs(Interface):
-
-    def breadcrumbs(self, view, recurs=False):
-        pass
-
-# XXX deprecates in favor of ITreeAdapter
-class ITree(Interface):
-
-    def parent(self):
-        """returns the parent entity"""
-
-    def children(self):
-        """returns the item's children"""
-
-    def children_rql(self):
-        """XXX returns RQL to get children"""
-
-    def iterchildren(self):
-        """iterates over the item's children"""
-
-    def is_leaf(self):
-        """returns true if this node as no child"""
-
-    def is_root(self):
-        """returns true if this node has no parent"""
-
-    def root(self):
-        """returns the root object"""
-
--- a/mixins.py	Mon Aug 26 16:12:30 2013 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,308 +0,0 @@
-# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
-#
-# This file is part of CubicWeb.
-#
-# CubicWeb is free software: you can redistribute it and/or modify it under the
-# terms of the GNU Lesser General Public License as published by the Free
-# Software Foundation, either version 2.1 of the License, or (at your option)
-# any later version.
-#
-# CubicWeb is distributed in the hope that it will be useful, but WITHOUT
-# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
-# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
-# details.
-#
-# You should have received a copy of the GNU Lesser General Public License along
-# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
-"""mixins of entity/views organized somewhat in a graph or tree structure"""
-__docformat__ = "restructuredtext en"
-
-from itertools import chain
-
-from logilab.common.decorators import cached
-from logilab.common.deprecation import deprecated, class_deprecated
-
-from cubicweb.predicates import implements
-from cubicweb.interfaces import ITree
-
-
-class TreeMixIn(object):
-    """base tree-mixin implementing the tree interface
-
-    This mixin has to be inherited explicitly and configured using the
-    tree_attribute, parent_target and children_target class attribute to
-    benefit from this default implementation
-    """
-    __metaclass__ = class_deprecated
-    __deprecation_warning__ = '[3.9] TreeMixIn is deprecated, use/override ITreeAdapter instead (%(cls)s)'
-
-    tree_attribute = None
-    # XXX misnamed
-    parent_target = 'subject'
-    children_target = 'object'
-
-    def different_type_children(self, entities=True):
-        """return children entities of different type as this entity.
-
-        according to the `entities` parameter, return entity objects or the
-        equivalent result set
-        """
-        res = self.related(self.tree_attribute, self.children_target,
-                           entities=entities)
-        if entities:
-            return [e for e in res if e.e_schema != self.e_schema]
-        return res.filtered_rset(lambda x: x.e_schema != self.e_schema, self.cw_col)
-
-    def same_type_children(self, entities=True):
-        """return children entities of the same type as this entity.
-
-        according to the `entities` parameter, return entity objects or the
-        equivalent result set
-        """
-        res = self.related(self.tree_attribute, self.children_target,
-                           entities=entities)
-        if entities:
-            return [e for e in res if e.e_schema == self.e_schema]
-        return res.filtered_rset(lambda x: x.e_schema is self.e_schema, self.cw_col)
-
-    def iterchildren(self, _done=None):
-        if _done is None:
-            _done = set()
-        for child in self.children():
-            if child.eid in _done:
-                self.error('loop in %s tree: %s', self.__regid__.lower(), child)
-                continue
-            yield child
-            _done.add(child.eid)
-
-    def prefixiter(self, _done=None):
-        if _done is None:
-            _done = set()
-        if self.eid in _done:
-            return
-        _done.add(self.eid)
-        yield self
-        for child in self.same_type_children():
-            for entity in child.prefixiter(_done):
-                yield entity
-
-    @cached
-    def path(self):
-        """returns the list of eids from the root object to this object"""
-        path = []
-        parent = self
-        while parent:
-            if parent.eid in path:
-                self.error('loop in %s tree: %s', self.__regid__.lower(), parent)
-                break
-            path.append(parent.eid)
-            try:
-                # check we are not leaving the tree
-                if (parent.tree_attribute != self.tree_attribute or
-                    parent.parent_target != self.parent_target):
-                    break
-                parent = parent.parent()
-            except AttributeError:
-                break
-
-        path.reverse()
-        return path
-
-    def iterparents(self, strict=True):
-        def _uptoroot(self):
-            curr = self
-            while True:
-                curr = curr.parent()
-                if curr is None:
-                    break
-                yield curr
-        if not strict:
-            return chain([self], _uptoroot(self))
-        return _uptoroot(self)
-
-    ## ITree interface ########################################################
-    def parent(self):
-        """return the parent entity if any, else None (e.g. if we are on the
-        root
-        """
-        try:
-            return self.related(self.tree_attribute, self.parent_target,
-                                entities=True)[0]
-        except (KeyError, IndexError):
-            return None
-
-    def children(self, entities=True, sametype=False):
-        """return children entities
-
-        according to the `entities` parameter, return entity objects or the
-        equivalent result set
-        """
-        if sametype:
-            return self.same_type_children(entities)
-        else:
-            return self.related(self.tree_attribute, self.children_target,
-                                entities=entities)
-
-    def children_rql(self):
-        return self.cw_related_rql(self.tree_attribute, self.children_target)
-
-    def is_leaf(self):
-        return len(self.children()) == 0
-
-    def is_root(self):
-        return self.parent() is None
-
-    def root(self):
-        """return the root object"""
-        return self._cw.entity_from_eid(self.path()[0])
-
-
-class EmailableMixIn(object):
-    """base mixin providing the default get_email() method used by
-    the massmailing view
-
-    NOTE: The default implementation is based on the
-    primary_email / use_email scheme
-    """
-    @deprecated("[3.9] use entity.cw_adapt_to('IEmailable').get_email()")
-    def get_email(self):
-        if getattr(self, 'primary_email', None):
-            return self.primary_email[0].address
-        if getattr(self, 'use_email', None):
-            return self.use_email[0].address
-        return None
-
-
-"""pluggable mixins system: plug classes registered in MI_REL_TRIGGERS on entity
-classes which have the relation described by the dict's key.
-
-NOTE: pluggable mixins can't override any method of the 'explicit' user classes tree
-(eg without plugged classes). This includes bases Entity and AnyEntity classes.
-"""
-MI_REL_TRIGGERS = {
-    ('primary_email',   'subject'): EmailableMixIn,
-    ('use_email',   'subject'): EmailableMixIn,
-    }
-
-
-# XXX move to cubicweb.web.views.treeview once we delete usage from this file
-def _done_init(done, view, row, col):
-    """handle an infinite recursion safety belt"""
-    if done is None:
-        done = set()
-    entity = view.cw_rset.get_entity(row, col)
-    if entity.eid in done:
-        msg = entity._cw._('loop in %(rel)s relation (%(eid)s)') % {
-            'rel': entity.cw_adapt_to('ITree').tree_relation,
-            'eid': entity.eid
-            }
-        return None, msg
-    done.add(entity.eid)
-    return done, entity
-
-
-class TreeViewMixIn(object):
-    """a recursive tree view"""
-    __metaclass__ = class_deprecated
-    __deprecation_warning__ = '[3.9] TreeViewMixIn is deprecated, use/override BaseTreeView instead (%(cls)s)'
-
-    __regid__ = 'tree'
-    __select__ = implements(ITree, warn=False)
-    item_vid = 'treeitem'
-
-    def call(self, done=None, **kwargs):
-        if done is None:
-            done = set()
-        super(TreeViewMixIn, self).call(done=done, **kwargs)
-
-    def cell_call(self, row, col=0, vid=None, done=None, maxlevel=None, **kwargs):
-        assert maxlevel is None or maxlevel > 0
-        done, entity = _done_init(done, self, row, col)
-        if done is None:
-            # entity is actually an error message
-            self.w(u'<li class="badcontent">%s</li>' % entity)
-            return
-        self.open_item(entity)
-        entity.view(vid or self.item_vid, w=self.w, **kwargs)
-        if maxlevel is not None:
-            maxlevel -= 1
-            if maxlevel == 0:
-                self.close_item(entity)
-                return
-        relatedrset = entity.children(entities=False)
-        self.wview(self.__regid__, relatedrset, 'null', done=done,
-                   maxlevel=maxlevel, **kwargs)
-        self.close_item(entity)
-
-    def open_item(self, entity):
-        self.w(u'<li class="%s">\n' % entity.cw_etype.lower())
-    def close_item(self, entity):
-        self.w(u'</li>\n')
-
-
-class TreePathMixIn(object):
-    """a recursive path view"""
-    __metaclass__ = class_deprecated
-    __deprecation_warning__ = '[3.9] TreePathMixIn is deprecated, use/override TreePathView instead (%(cls)s)'
-    __regid__ = 'path'
-    item_vid = 'oneline'
-    separator = u'&#160;&gt;&#160;'
-
-    def call(self, **kwargs):
-        self.w(u'<div class="pathbar">')
-        super(TreePathMixIn, self).call(**kwargs)
-        self.w(u'</div>')
-
-    def cell_call(self, row, col=0, vid=None, done=None, **kwargs):
-        done, entity = _done_init(done, self, row, col)
-        if done is None:
-            # entity is actually an error message
-            self.w(u'<span class="badcontent">%s</span>' % entity)
-            return
-        parent = entity.parent()
-        if parent:
-            parent.view(self.__regid__, w=self.w, done=done)
-            self.w(self.separator)
-        entity.view(vid or self.item_vid, w=self.w)
-
-
-class ProgressMixIn(object):
-    """provide a default implementations for IProgress interface methods"""
-    __metaclass__ = class_deprecated
-    __deprecation_warning__ = '[3.9] ProgressMixIn is deprecated, use/override IProgressAdapter instead (%(cls)s)'
-
-    @property
-    def cost(self):
-        return self.progress_info()['estimated']
-
-    @property
-    def revised_cost(self):
-        return self.progress_info().get('estimatedcorrected', self.cost)
-
-    @property
-    def done(self):
-        return self.progress_info()['done']
-
-    @property
-    def todo(self):
-        return self.progress_info()['todo']
-
-    @cached
-    def progress_info(self):
-        raise NotImplementedError()
-
-    def finished(self):
-        return not self.in_progress()
-
-    def in_progress(self):
-        raise NotImplementedError()
-
-    def progress(self):
-        try:
-            return 100. * self.done / self.revised_cost
-        except ZeroDivisionError:
-            # total cost is 0 : if everything was estimated, task is completed
-            if self.progress_info().get('notestimated'):
-                return 0.
-            return 100
--- a/predicates.py	Mon Aug 26 16:12:30 2013 +0200
+++ b/predicates.py	Mon Aug 26 16:14:09 2013 +0200
@@ -204,27 +204,6 @@
 # remember, these imports are there for bw compat only
 __BACKWARD_COMPAT_IMPORTS = (yes,)
 
-def score_interface(etypesreg, eclass, iface):
-    """Return XXX if the give object (maybe an instance or class) implements
-    the interface.
-    """
-    if getattr(iface, '__registry__', None) == 'etypes':
-        # adjust score if the interface is an entity class
-        parents, any = etypesreg.parent_classes(eclass.__regid__)
-        if iface is eclass:
-            return len(parents) + 4
-        if iface is any: # Any
-            return 1
-        for index, basecls in enumerate(reversed(parents)):
-            if iface is basecls:
-                return index + 3
-        return 0
-    # XXX iface in implements deprecated in 3.9
-    if implements_iface(eclass, iface):
-        # implementing an interface takes precedence other special Any interface
-        return 2
-    return 0
-
 
 # abstract predicates / mixin helpers ###########################################
 
@@ -745,53 +724,6 @@
         return 1 # necessarily true if we're there
 
 
-class implements(EClassPredicate):
-    """Return non-zero score for entity that are of the given type(s) or
-    implements at least one of the given interface(s). If multiple arguments are
-    given, matching one of them is enough.
-
-    Entity types should be given as string, the corresponding class will be
-    fetched from the entity types registry at selection time.
-
-    See :class:`~cubicweb.predicates.EClassPredicate` documentation for entity
-    class lookup / score rules according to the input context.
-
-    .. note::
-
-       when interface is an entity class, the score will reflect class
-       proximity so the most specific object will be selected.
-
-    .. note::
-
-       deprecated in cubicweb >= 3.9, use either
-       :class:`~cubicweb.predicates.is_instance` or
-       :class:`~cubicweb.predicates.adaptable`.
-    """
-
-    def __init__(self, *expected_ifaces, **kwargs):
-        emit_warn = kwargs.pop('warn', True)
-        super(implements, self).__init__(**kwargs)
-        self.expected_ifaces = expected_ifaces
-        if emit_warn:
-            warn('[3.9] implements predicate is deprecated, use either '
-                 'is_instance or adaptable', DeprecationWarning, stacklevel=2)
-
-    def __str__(self):
-        return '%s(%s)' % (self.__class__.__name__,
-                           ','.join(str(s) for s in self.expected_ifaces))
-
-    def score_class(self, eclass, req):
-        score = 0
-        etypesreg = req.vreg['etypes']
-        for iface in self.expected_ifaces:
-            if isinstance(iface, basestring):
-                # entity type
-                try:
-                    iface = etypesreg.etype_class(iface)
-                except KeyError:
-                    continue # entity type not in the schema
-            score += score_interface(etypesreg, eclass, iface)
-        return score
 
 def _reset_is_instance_cache(vreg):
     vreg._is_instance_predicate_cache = {}
--- a/view.py	Mon Aug 26 16:12:30 2013 +0200
+++ b/view.py	Mon Aug 26 16:14:09 2013 +0200
@@ -558,34 +558,6 @@
     __registry__ = 'adapters'
 
 
-def implements_adapter_compat(iface):
-    def _pre39_compat(func):
-        def decorated(self, *args, **kwargs):
-            entity = self.entity
-            if hasattr(entity, func.__name__):
-                warn('[3.9] %s method is deprecated, define it on a custom '
-                     '%s for %s instead' % (func.__name__, iface,
-                                            entity.__class__),
-                     DeprecationWarning)
-                member = getattr(entity, func.__name__)
-                if callable(member):
-                    return member(*args, **kwargs)
-                return member
-            return func(self, *args, **kwargs)
-        decorated.decorated = func
-        return decorated
-    return _pre39_compat
-
-
-def unwrap_adapter_compat(cls):
-    parent = cls.__bases__[0]
-    for member_name in dir(parent):
-        member = getattr(parent, member_name)
-        if isinstance(member, types.MethodType) and hasattr(member.im_func, 'decorated') and not member_name in cls.__dict__:
-            method = new.instancemethod(member.im_func.decorated, None, cls)
-            setattr(cls, member_name, method)
-
-
 class auto_unwrap_bw_compat(type):
     def __new__(mcs, name, bases, classdict):
         cls = type.__new__(mcs, name, bases, classdict)
@@ -596,7 +568,6 @@
 
 class EntityAdapter(Adapter):
     """base class for entity adapters (eg adapt an entity to an interface)"""
-    __metaclass__ = auto_unwrap_bw_compat
     def __init__(self, _cw, **kwargs):
         try:
             self.entity = kwargs.pop('entity')
--- a/web/views/calendar.py	Mon Aug 26 16:12:30 2013 +0200
+++ b/web/views/calendar.py	Mon Aug 26 16:14:09 2013 +0200
@@ -27,9 +27,8 @@
 from logilab.common.date import todatetime
 
 from cubicweb.utils import json_dumps, make_uid
-from cubicweb.interfaces import ICalendarable
-from cubicweb.predicates import implements, adaptable
-from cubicweb.view import EntityView, EntityAdapter, implements_adapter_compat
+from cubicweb.predicates import adaptable
+from cubicweb.view import EntityView, EntityAdapter
 
 # useful constants & functions ################################################
 
@@ -46,16 +45,14 @@
 class ICalendarableAdapter(EntityAdapter):
     __needs_bw_compat__ = True
     __regid__ = 'ICalendarable'
-    __select__ = implements(ICalendarable, warn=False) # XXX for bw compat, should be abstract
+    __abstract__ = True
 
     @property
-    @implements_adapter_compat('ICalendarable')
     def start(self):
         """return start date"""
         raise NotImplementedError
 
     @property
-    @implements_adapter_compat('ICalendarable')
     def stop(self):
         """return stop date"""
         raise NotImplementedError
--- a/web/views/editcontroller.py	Mon Aug 26 16:12:30 2013 +0200
+++ b/web/views/editcontroller.py	Mon Aug 26 16:14:09 2013 +0200
@@ -28,7 +28,7 @@
 from rql.utils import rqlvar_maker
 
 from cubicweb import Binary, ValidationError
-from cubicweb.view import EntityAdapter, implements_adapter_compat
+from cubicweb.view import EntityAdapter
 from cubicweb.predicates import is_instance
 from cubicweb.web import (INTERNAL_FIELD_VALUE, RequestError, NothingToEdit,
                           ProcessFormError)
@@ -36,7 +36,6 @@
 
 
 class IEditControlAdapter(EntityAdapter):
-    __needs_bw_compat__ = True
     __regid__ = 'IEditControl'
     __select__ = is_instance('Any')
 
@@ -47,7 +46,6 @@
                  DeprecationWarning)
         super(IEditControlAdapter, self).__init__(_cw, **kwargs)
 
-    @implements_adapter_compat('IEditControl')
     def after_deletion_path(self):
         """return (path, parameters) which should be used as redirect
         information when this entity is being deleted
@@ -57,7 +55,6 @@
             return parent.rest_path(), {}
         return str(self.entity.e_schema).lower(), {}
 
-    @implements_adapter_compat('IEditControl')
     def pre_web_edit(self):
         """callback called by the web editcontroller when an entity will be
         created/modified, to let a chance to do some entity specific stuff.
--- a/web/views/ibreadcrumbs.py	Mon Aug 26 16:12:30 2013 +0200
+++ b/web/views/ibreadcrumbs.py	Mon Aug 26 16:14:09 2013 +0200
@@ -24,7 +24,6 @@
 
 from logilab.mtconverter import xml_escape
 
-#from cubicweb.interfaces import IBreadCrumbs
 from cubicweb import tags, uilib
 from cubicweb.entity import Entity
 from cubicweb.predicates import (is_instance, one_line_rset, adaptable,
@@ -35,15 +34,6 @@
 # don't use AnyEntity since this may cause bug with isinstance() due to reloading
 
 
-# ease bw compat
-def ibreadcrumb_adapter(entity):
-    if hasattr(entity, 'breadcrumbs'):
-        warn('[3.9] breadcrumbs() method is deprecated, define a custom '
-             'IBreadCrumbsAdapter for %s instead' % entity.__class__,
-             DeprecationWarning)
-        return entity
-    return entity.cw_adapt_to('IBreadCrumbs')
-
 
 class IBreadCrumbsAdapter(EntityAdapter):
     """adapters for entities which can be"located" on some path to display in
@@ -53,11 +43,6 @@
     __select__ = is_instance('Any', accept_none=False)
 
     def parent_entity(self):
-        if hasattr(self.entity, 'parent') and callable(self.entity.parent):
-            warn('[3.9] parent() method is deprecated, define a '
-                 'custom IBreadCrumbsAdapter/ITreeAdapter for %s instead'
-                 % self.entity.__class__, DeprecationWarning)
-            return self.entity.parent()
         itree = self.entity.cw_adapt_to('ITree')
         if itree is not None:
             return itree.parent()
@@ -94,7 +79,7 @@
                 self.error('cycle in breadcrumbs for entity %s' % self.entity)
                 return []
             _recurs.add(parent.eid)
-            adapter = ibreadcrumb_adapter(parent)
+            adapter = parent.cw_adapt_to('IBreadCrumbs')
             path = adapter.breadcrumbs(view, _recurs) + [self.entity]
         else:
             path = [self.entity]
@@ -125,7 +110,7 @@
             entity = self.cw_extra_kwargs['entity']
         except KeyError:
             entity = self.cw_rset.get_entity(0, 0)
-        adapter = ibreadcrumb_adapter(entity)
+        adapter = entity.cw_adapt_to('IBreadCrumbs')
         view = self.cw_extra_kwargs.get('view')
         path = adapter.breadcrumbs(view)
         if path:
--- a/web/views/navigation.py	Mon Aug 26 16:12:30 2013 +0200
+++ b/web/views/navigation.py	Mon Aug 26 16:14:09 2013 +0200
@@ -55,10 +55,9 @@
 from logilab.mtconverter import xml_escape
 from logilab.common.deprecation import deprecated
 
-from cubicweb.predicates import (paginated_rset, sorted_rset,
-                                adaptable, implements)
+from cubicweb.predicates import paginated_rset, sorted_rset, adaptable
 from cubicweb.uilib import cut
-from cubicweb.view import EntityAdapter, implements_adapter_compat
+from cubicweb.view import EntityAdapter
 from cubicweb.web.component import EmptyComponent, EntityCtxComponent, NavigationComponent
 
 
@@ -324,7 +323,6 @@
 View.handle_pagination = False
 
 
-from cubicweb.interfaces import IPrevNext
 
 class IPrevNextAdapter(EntityAdapter):
     """Interface for entities which can be linked to a previous and/or next
@@ -335,14 +333,12 @@
     """
     __needs_bw_compat__ = True
     __regid__ = 'IPrevNext'
-    __select__ = implements(IPrevNext, warn=False) # XXX for bw compat, else should be abstract
+    __abstract__ = True
 
-    @implements_adapter_compat('IPrevNext')
     def next_entity(self):
         """return the 'next' entity"""
         raise NotImplementedError
 
-    @implements_adapter_compat('IPrevNext')
     def previous_entity(self):
         """return the 'previous' entity"""
         raise NotImplementedError
--- a/web/views/treeview.py	Mon Aug 26 16:12:30 2013 +0200
+++ b/web/views/treeview.py	Mon Aug 26 16:14:09 2013 +0200
@@ -29,13 +29,26 @@
 from cubicweb.utils import make_uid, json
 from cubicweb.predicates import adaptable
 from cubicweb.view import EntityView
-from cubicweb.mixins import _done_init
 from cubicweb.web.views import baseviews
 from cubicweb.web.views.ajaxcontroller import ajaxfunc
 
 def treecookiename(treeid):
     return str('%s-treestate' % treeid)
 
+def _done_init(done, view, row, col):
+    """handle an infinite recursion safety belt"""
+    if done is None:
+        done = set()
+    entity = view.cw_rset.get_entity(row, col)
+    if entity.eid in done:
+        msg = entity._cw._('loop in %(rel)s relation (%(eid)s)') % {
+            'rel': entity.cw_adapt_to('ITree').tree_relation,
+            'eid': entity.eid
+            }
+        return None, msg
+    done.add(entity.eid)
+    return done, entity
+
 
 class BaseTreeView(baseviews.ListView):
     """base tree view"""
--- a/web/views/xmlrss.py	Mon Aug 26 16:12:30 2013 +0200
+++ b/web/views/xmlrss.py	Mon Aug 26 16:14:09 2013 +0200
@@ -28,7 +28,6 @@
 from cubicweb.predicates import (is_instance, non_final_entity, one_line_rset,
                                 appobject_selectable, adaptable)
 from cubicweb.view import EntityView, EntityAdapter, AnyRsetView, Component
-from cubicweb.view import implements_adapter_compat
 from cubicweb.uilib import simple_sgml_tag
 from cubicweb.web import httpcache, component
 
@@ -185,7 +184,6 @@
     __regid__ = 'IFeed'
     __select__ = is_instance('Any')
 
-    @implements_adapter_compat('IFeed')
     def rss_feed_url(self):
         """return an url to the rss feed for this entity"""
         return self.entity.absolute_url(vid='rss')