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' |