19 framework itself. |
19 framework itself. |
20 """ |
20 """ |
21 |
21 |
22 __docformat__ = "restructuredtext en" |
22 __docformat__ = "restructuredtext en" |
23 |
23 |
|
24 from itertools import chain |
|
25 |
24 from logilab.mtconverter import TransformError |
26 from logilab.mtconverter import TransformError |
|
27 from logilab.common.decorators import cached |
25 |
28 |
26 from cubicweb.view import EntityAdapter, implements_adapter_compat |
29 from cubicweb.view import EntityAdapter, implements_adapter_compat |
27 from cubicweb.selectors import implements, relation_possible |
30 from cubicweb.selectors import implements, relation_possible |
28 from cubicweb.interfaces import IDownloadable |
31 from cubicweb.interfaces import IDownloadable, ITree |
29 |
32 |
30 |
33 |
31 class IEmailableAdapter(EntityAdapter): |
34 class IEmailableAdapter(EntityAdapter): |
32 __regid__ = 'IEmailable' |
35 __regid__ = 'IEmailable' |
33 __select__ = relation_possible('primary_email') | relation_possible('use_email') |
36 __select__ = relation_possible('primary_email') | relation_possible('use_email') |
164 raise NotImplementedError |
167 raise NotImplementedError |
165 @implements_adapter_compat('IDownloadable') |
168 @implements_adapter_compat('IDownloadable') |
166 def download_data(self): |
169 def download_data(self): |
167 """return actual data of the downloadable content""" |
170 """return actual data of the downloadable content""" |
168 raise NotImplementedError |
171 raise NotImplementedError |
|
172 |
|
173 |
|
174 class ITreeAdapter(EntityAdapter): |
|
175 """This adapter has to be overriden to be configured using the |
|
176 tree_relation, child_role and parent_role class attributes to |
|
177 benefit from this default implementation |
|
178 """ |
|
179 __regid__ = 'ITree' |
|
180 __select__ = implements(ITree) # XXX for bw compat, else should be abstract |
|
181 |
|
182 tree_relation = None |
|
183 child_role = 'subject' |
|
184 parent_role = 'object' |
|
185 |
|
186 @implements_adapter_compat('ITree') |
|
187 def children_rql(self): |
|
188 """returns RQL to get children |
|
189 |
|
190 XXX should be removed from the public interface |
|
191 """ |
|
192 return self.entity.cw_related_rql(self.tree_relation, self.parent_role) |
|
193 |
|
194 @implements_adapter_compat('ITree') |
|
195 def different_type_children(self, entities=True): |
|
196 """return children entities of different type as this entity. |
|
197 |
|
198 according to the `entities` parameter, return entity objects or the |
|
199 equivalent result set |
|
200 """ |
|
201 res = self.entity.related(self.tree_relation, self.parent_role, |
|
202 entities=entities) |
|
203 eschema = self.entity.e_schema |
|
204 if entities: |
|
205 return [e for e in res if e.e_schema != eschema] |
|
206 return res.filtered_rset(lambda x: x.e_schema != eschema, self.entity.cw_col) |
|
207 |
|
208 @implements_adapter_compat('ITree') |
|
209 def same_type_children(self, entities=True): |
|
210 """return children entities of the same type as this entity. |
|
211 |
|
212 according to the `entities` parameter, return entity objects or the |
|
213 equivalent result set |
|
214 """ |
|
215 res = self.entity.related(self.tree_relation, self.parent_role, |
|
216 entities=entities) |
|
217 eschema = self.entity.e_schema |
|
218 if entities: |
|
219 return [e for e in res if e.e_schema == eschema] |
|
220 return res.filtered_rset(lambda x: x.e_schema is eschema, self.entity.cw_col) |
|
221 |
|
222 @implements_adapter_compat('ITree') |
|
223 def is_leaf(self): |
|
224 """returns true if this node as no child""" |
|
225 return len(self.children()) == 0 |
|
226 |
|
227 @implements_adapter_compat('ITree') |
|
228 def is_root(self): |
|
229 """returns true if this node has no parent""" |
|
230 return self.parent() is None |
|
231 |
|
232 @implements_adapter_compat('ITree') |
|
233 def root(self): |
|
234 """return the root object""" |
|
235 return self._cw.entity_from_eid(self.path()[0]) |
|
236 |
|
237 @implements_adapter_compat('ITree') |
|
238 def parent(self): |
|
239 """return the parent entity if any, else None (e.g. if we are on the |
|
240 root) |
|
241 """ |
|
242 try: |
|
243 return self.entity.related(self.tree_relation, self.child_role, |
|
244 entities=True)[0] |
|
245 except (KeyError, IndexError): |
|
246 return None |
|
247 |
|
248 @implements_adapter_compat('ITree') |
|
249 def children(self, entities=True, sametype=False): |
|
250 """return children entities |
|
251 |
|
252 according to the `entities` parameter, return entity objects or the |
|
253 equivalent result set |
|
254 """ |
|
255 if sametype: |
|
256 return self.same_type_children(entities) |
|
257 else: |
|
258 return self.entity.related(self.tree_relation, self.parent_role, |
|
259 entities=entities) |
|
260 |
|
261 @implements_adapter_compat('ITree') |
|
262 def iterparents(self, strict=True): |
|
263 def _uptoroot(self): |
|
264 curr = self |
|
265 while True: |
|
266 curr = curr.parent() |
|
267 if curr is None: |
|
268 break |
|
269 yield curr |
|
270 curr = curr.cw_adapt_to('ITree') |
|
271 if not strict: |
|
272 return chain([self.entity], _uptoroot(self)) |
|
273 return _uptoroot(self) |
|
274 |
|
275 @implements_adapter_compat('ITree') |
|
276 def iterchildren(self, _done=None): |
|
277 """iterates over the item's children""" |
|
278 if _done is None: |
|
279 _done = set() |
|
280 for child in self.children(): |
|
281 if child.eid in _done: |
|
282 self.error('loop in %s tree', child.__regid__.lower()) |
|
283 continue |
|
284 yield child |
|
285 _done.add(child.eid) |
|
286 |
|
287 @implements_adapter_compat('ITree') |
|
288 def prefixiter(self, _done=None): |
|
289 if _done is None: |
|
290 _done = set() |
|
291 if self.entity.eid in _done: |
|
292 return |
|
293 _done.add(self.entity.eid) |
|
294 yield self.entity |
|
295 for child in self.same_type_children(): |
|
296 for entity in child.cw_adapt_to('ITree').prefixiter(_done): |
|
297 yield entity |
|
298 |
|
299 @cached |
|
300 @implements_adapter_compat('ITree') |
|
301 def path(self): |
|
302 """returns the list of eids from the root object to this object""" |
|
303 path = [] |
|
304 adapter = self |
|
305 entity = adapter.entity |
|
306 while entity is not None: |
|
307 if entity.eid in path: |
|
308 self.error('loop in %s tree', entity.__regid__.lower()) |
|
309 break |
|
310 path.append(entity.eid) |
|
311 try: |
|
312 # check we are not jumping to another tree |
|
313 if (adapter.tree_relation != self.tree_relation or |
|
314 adapter.child_role != self.child_role): |
|
315 break |
|
316 entity = adapter.parent() |
|
317 adapter = entity.cw_adapt_to('ITree') |
|
318 except AttributeError: |
|
319 break |
|
320 path.reverse() |
|
321 return path |
|
322 |