diff -r 2c3e83817a8e -r 0e2af244dea5 entities/adapters.py --- a/entities/adapters.py Thu Jun 10 16:23:07 2010 +0200 +++ b/entities/adapters.py Thu Jun 10 16:25:15 2010 +0200 @@ -21,11 +21,14 @@ __docformat__ = "restructuredtext en" +from itertools import chain + from logilab.mtconverter import TransformError +from logilab.common.decorators import cached from cubicweb.view import EntityAdapter, implements_adapter_compat from cubicweb.selectors import implements, relation_possible -from cubicweb.interfaces import IDownloadable +from cubicweb.interfaces import IDownloadable, ITree class IEmailableAdapter(EntityAdapter): @@ -166,3 +169,154 @@ def download_data(self): """return actual data of the downloadable content""" raise NotImplementedError + + +class ITreeAdapter(EntityAdapter): + """This adapter has to be overriden to be configured using the + tree_relation, child_role and parent_role class attributes to + benefit from this default implementation + """ + __regid__ = 'ITree' + __select__ = implements(ITree) # XXX for bw compat, else should be abstract + + tree_relation = None + child_role = 'subject' + parent_role = 'object' + + @implements_adapter_compat('ITree') + def children_rql(self): + """returns RQL to get children + + XXX should be removed from the public interface + """ + return self.entity.cw_related_rql(self.tree_relation, self.parent_role) + + @implements_adapter_compat('ITree') + 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.entity.related(self.tree_relation, self.parent_role, + entities=entities) + eschema = self.entity.e_schema + if entities: + 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) + + @implements_adapter_compat('ITree') + 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.entity.related(self.tree_relation, self.parent_role, + entities=entities) + eschema = self.entity.e_schema + if entities: + 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) + + @implements_adapter_compat('ITree') + def is_leaf(self): + """returns true if this node as no child""" + return len(self.children()) == 0 + + @implements_adapter_compat('ITree') + def is_root(self): + """returns true if this node has no parent""" + return self.parent() is None + + @implements_adapter_compat('ITree') + def root(self): + """return the root object""" + return self._cw.entity_from_eid(self.path()[0]) + + @implements_adapter_compat('ITree') + def parent(self): + """return the parent entity if any, else None (e.g. if we are on the + root) + """ + try: + return self.entity.related(self.tree_relation, self.child_role, + entities=True)[0] + except (KeyError, IndexError): + return None + + @implements_adapter_compat('ITree') + 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.entity.related(self.tree_relation, self.parent_role, + entities=entities) + + @implements_adapter_compat('ITree') + def iterparents(self, strict=True): + def _uptoroot(self): + curr = self + while True: + curr = curr.parent() + if curr is None: + break + yield curr + curr = curr.cw_adapt_to('ITree') + if not strict: + return chain([self.entity], _uptoroot(self)) + return _uptoroot(self) + + @implements_adapter_compat('ITree') + def iterchildren(self, _done=None): + """iterates over the item's children""" + if _done is None: + _done = set() + for child in self.children(): + if child.eid in _done: + self.error('loop in %s tree', child.__regid__.lower()) + continue + yield child + _done.add(child.eid) + + @implements_adapter_compat('ITree') + def prefixiter(self, _done=None): + if _done is None: + _done = set() + if self.entity.eid in _done: + return + _done.add(self.entity.eid) + yield self.entity + for child in self.same_type_children(): + for entity in child.cw_adapt_to('ITree').prefixiter(_done): + yield entity + + @cached + @implements_adapter_compat('ITree') + def path(self): + """returns the list of eids from the root object to this object""" + path = [] + adapter = self + entity = adapter.entity + while entity is not None: + if entity.eid in path: + self.error('loop in %s tree', entity.__regid__.lower()) + break + path.append(entity.eid) + try: + # check we are not jumping to another tree + if (adapter.tree_relation != self.tree_relation or + adapter.child_role != self.child_role): + break + entity = adapter.parent() + adapter = entity.cw_adapt_to('ITree') + except AttributeError: + break + path.reverse() + return path +