Commit b1b9e2a0 authored by Aurelien Campeas's avatar Aurelien Campeas
Browse files

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.
parent 46f41c3e1443
......@@ -211,8 +211,7 @@ from yams.constraints import BASE_CONVERTERS
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 @@ def cleanup_uicfg_compat():
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 @@ class CWRegistryStore(RegistryStore):
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 @@ class CWRegistryStore(RegistryStore):
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 @@ class CWRegistryStore(RegistryStore):
"""
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 @@ class CWRegistryStore(RegistryStore):
# 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]
......
......@@ -28,3 +28,6 @@ Deprecated Code Drops
(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
......@@ -10,13 +10,7 @@ concerns in object oriented applications.
.. _`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`):
......
......@@ -194,8 +194,6 @@ query that correctly order files by their `data_name` attribute.
.. 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 @@ folder, ordered similarly (eg by their `data_name` attribute). We set
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 @@ machine (using `scp` for instance) to restore it and start migration: ::
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.
......
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 @@ the cube's :file:`uiprops.py` file:
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 @@ friends...
.. _`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
......@@ -28,9 +28,7 @@ from logilab.mtconverter import TransformError
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 IEmailableAdapter(view.EntityAdapter):
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 @@ def merge_weight_dict(maindict, newdict):
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 @@ class ITreeAdapter(view.EntityAdapter):
.. 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 @@ class ITreeAdapter(view.EntityAdapter):
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 @@ class ITreeAdapter(view.EntityAdapter):
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 @@ class ITreeAdapter(view.EntityAdapter):
except (KeyError, IndexError):
return None
@view.implements_adapter_compat('ITree')
def children(self, entities=True, sametype=False):
"""Return children entities.
......@@ -307,7 +284,6 @@ class ITreeAdapter(view.EntityAdapter):
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 @@ class ITreeAdapter(view.EntityAdapter):
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 @@ class ITreeAdapter(view.EntityAdapter):
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 @@ class ITreeAdapter(view.EntityAdapter):
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."""
......
......@@ -25,7 +25,6 @@ from logilab.common.interface import implements
from cubicweb.devtools.testlib import CubicWebTC
from cubicweb.interfaces import IMileStone, ICalendarable
from cubicweb.entities import AnyEntity
......@@ -134,27 +133,6 @@ class CWUserTC(BaseEntityTC):
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):
......
......@@ -32,7 +32,6 @@ from logilab.common.compat import any
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 @@ class TrInfo(AnyEntity):
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'
......
......@@ -42,7 +42,6 @@ from cubicweb.schema import (RQLVocabularyConstraint, RQLConstraint,
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 @@ class Entity(AppObject):
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 @@ class Entity(AppObject):
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):
......
# 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):