web/views/treeview.py
changeset 5716 0e2af244dea5
parent 5696 98d390c28edb
child 5718 8d246203730a
equal deleted inserted replaced
5715:2c3e83817a8e 5716:0e2af244dea5
    20 """
    20 """
    21 
    21 
    22 __docformat__ = "restructuredtext en"
    22 __docformat__ = "restructuredtext en"
    23 
    23 
    24 from warnings import warn
    24 from warnings import warn
    25 from itertools import chain
       
    26 
    25 
    27 from logilab.mtconverter import xml_escape
    26 from logilab.mtconverter import xml_escape
    28 from logilab.common.decorators import cached
       
    29 
    27 
    30 from cubicweb.utils import make_uid
    28 from cubicweb.utils import make_uid
    31 from cubicweb.selectors import implements, adaptable
    29 from cubicweb.selectors import adaptable
    32 from cubicweb.view import EntityView, EntityAdapter, implements_adapter_compat
    30 from cubicweb.view import EntityView
    33 from cubicweb.mixins import _done_init
    31 from cubicweb.mixins import _done_init
    34 from cubicweb.web import json
    32 from cubicweb.web import json
    35 from cubicweb.interfaces import ITree
       
    36 from cubicweb.web.views import baseviews
    33 from cubicweb.web.views import baseviews
    37 
    34 
    38 def treecookiename(treeid):
    35 def treecookiename(treeid):
    39     return str('%s-treestate' % treeid)
    36     return str('%s-treestate' % treeid)
    40 
       
    41 
       
    42 class ITreeAdapter(EntityAdapter):
       
    43     """This adapter has to be overriden to be configured using the
       
    44     tree_relation, child_role and parent_role class attributes to
       
    45     benefit from this default implementation
       
    46     """
       
    47     __regid__ = 'ITree'
       
    48     __select__ = implements(ITree) # XXX for bw compat, else should be abstract
       
    49 
       
    50     tree_relation = None
       
    51     child_role = 'subject'
       
    52     parent_role = 'object'
       
    53 
       
    54     @implements_adapter_compat('ITree')
       
    55     def children_rql(self):
       
    56         """returns RQL to get children
       
    57 
       
    58         XXX should be removed from the public interface
       
    59         """
       
    60         return self.entity.cw_related_rql(self.tree_relation, self.parent_role)
       
    61 
       
    62     @implements_adapter_compat('ITree')
       
    63     def different_type_children(self, entities=True):
       
    64         """return children entities of different type as this entity.
       
    65 
       
    66         according to the `entities` parameter, return entity objects or the
       
    67         equivalent result set
       
    68         """
       
    69         res = self.entity.related(self.tree_relation, self.parent_role,
       
    70                                   entities=entities)
       
    71         eschema = self.entity.e_schema
       
    72         if entities:
       
    73             return [e for e in res if e.e_schema != eschema]
       
    74         return res.filtered_rset(lambda x: x.e_schema != eschema, self.entity.cw_col)
       
    75 
       
    76     @implements_adapter_compat('ITree')
       
    77     def same_type_children(self, entities=True):
       
    78         """return children entities of the same type as this entity.
       
    79 
       
    80         according to the `entities` parameter, return entity objects or the
       
    81         equivalent result set
       
    82         """
       
    83         res = self.entity.related(self.tree_relation, self.parent_role,
       
    84                                   entities=entities)
       
    85         eschema = self.entity.e_schema
       
    86         if entities:
       
    87             return [e for e in res if e.e_schema == eschema]
       
    88         return res.filtered_rset(lambda x: x.e_schema is eschema, self.entity.cw_col)
       
    89 
       
    90     @implements_adapter_compat('ITree')
       
    91     def is_leaf(self):
       
    92         """returns true if this node as no child"""
       
    93         return len(self.children()) == 0
       
    94 
       
    95     @implements_adapter_compat('ITree')
       
    96     def is_root(self):
       
    97         """returns true if this node has no parent"""
       
    98         return self.parent() is None
       
    99 
       
   100     @implements_adapter_compat('ITree')
       
   101     def root(self):
       
   102         """return the root object"""
       
   103         return self._cw.entity_from_eid(self.path()[0])
       
   104 
       
   105     @implements_adapter_compat('ITree')
       
   106     def parent(self):
       
   107         """return the parent entity if any, else None (e.g. if we are on the
       
   108         root)
       
   109         """
       
   110         try:
       
   111             return self.entity.related(self.tree_relation, self.child_role,
       
   112                                        entities=True)[0]
       
   113         except (KeyError, IndexError):
       
   114             return None
       
   115 
       
   116     @implements_adapter_compat('ITree')
       
   117     def children(self, entities=True, sametype=False):
       
   118         """return children entities
       
   119 
       
   120         according to the `entities` parameter, return entity objects or the
       
   121         equivalent result set
       
   122         """
       
   123         if sametype:
       
   124             return self.same_type_children(entities)
       
   125         else:
       
   126             return self.entity.related(self.tree_relation, self.parent_role,
       
   127                                        entities=entities)
       
   128 
       
   129     @implements_adapter_compat('ITree')
       
   130     def iterparents(self, strict=True):
       
   131         def _uptoroot(self):
       
   132             curr = self
       
   133             while True:
       
   134                 curr = curr.parent()
       
   135                 if curr is None:
       
   136                     break
       
   137                 yield curr
       
   138                 curr = curr.cw_adapt_to('ITree')
       
   139         if not strict:
       
   140             return chain([self.entity], _uptoroot(self))
       
   141         return _uptoroot(self)
       
   142 
       
   143     @implements_adapter_compat('ITree')
       
   144     def iterchildren(self, _done=None):
       
   145         """iterates over the item's children"""
       
   146         if _done is None:
       
   147             _done = set()
       
   148         for child in self.children():
       
   149             if child.eid in _done:
       
   150                 self.error('loop in %s tree', child.__regid__.lower())
       
   151                 continue
       
   152             yield child
       
   153             _done.add(child.eid)
       
   154 
       
   155     @implements_adapter_compat('ITree')
       
   156     def prefixiter(self, _done=None):
       
   157         if _done is None:
       
   158             _done = set()
       
   159         if self.entity.eid in _done:
       
   160             return
       
   161         _done.add(self.entity.eid)
       
   162         yield self.entity
       
   163         for child in self.same_type_children():
       
   164             for entity in child.cw_adapt_to('ITree').prefixiter(_done):
       
   165                 yield entity
       
   166 
       
   167     @cached
       
   168     @implements_adapter_compat('ITree')
       
   169     def path(self):
       
   170         """returns the list of eids from the root object to this object"""
       
   171         path = []
       
   172         adapter = self
       
   173         entity = adapter.entity
       
   174         while entity is not None:
       
   175             if entity.eid in path:
       
   176                 self.error('loop in %s tree', entity.__regid__.lower())
       
   177                 break
       
   178             path.append(entity.eid)
       
   179             try:
       
   180                 # check we are not jumping to another tree
       
   181                 if (adapter.tree_relation != self.tree_relation or
       
   182                     adapter.child_role != self.child_role):
       
   183                     break
       
   184                 entity = adapter.parent()
       
   185                 adapter = entity.cw_adapt_to('ITree')
       
   186             except AttributeError:
       
   187                 break
       
   188         path.reverse()
       
   189         return path
       
   190 
    37 
   191 
    38 
   192 class BaseTreeView(baseviews.ListView):
    39 class BaseTreeView(baseviews.ListView):
   193     """base tree view"""
    40     """base tree view"""
   194     __regid__ = 'tree'
    41     __regid__ = 'tree'