1 # copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved. |
|
2 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr |
|
3 # |
|
4 # This file is part of CubicWeb. |
|
5 # |
|
6 # CubicWeb is free software: you can redistribute it and/or modify it under the |
|
7 # terms of the GNU Lesser General Public License as published by the Free |
|
8 # Software Foundation, either version 2.1 of the License, or (at your option) |
|
9 # any later version. |
|
10 # |
|
11 # CubicWeb is distributed in the hope that it will be useful, but WITHOUT |
|
12 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
|
13 # FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more |
|
14 # details. |
|
15 # |
|
16 # You should have received a copy of the GNU Lesser General Public License along |
|
17 # with CubicWeb. If not, see <http://www.gnu.org/licenses/>. |
|
18 """ |
|
19 HTML views |
|
20 ~~~~~~~~~~ |
|
21 |
|
22 Special views |
|
23 ````````````` |
|
24 |
|
25 .. autoclass:: NullView |
|
26 .. autoclass:: NoResultView |
|
27 .. autoclass:: FinalView |
|
28 |
|
29 |
|
30 Base entity views |
|
31 ````````````````` |
|
32 |
|
33 .. autoclass:: InContextView |
|
34 .. autoclass:: OutOfContextView |
|
35 .. autoclass:: OneLineView |
|
36 |
|
37 Those are used to display a link to an entity, whose label depends on the entity |
|
38 having to be displayed in or out of context (of another entity): some entities |
|
39 make sense in the context of another entity. For instance, the `Version` of a |
|
40 `Project` in forge. So one may expect that 'incontext' will be called when |
|
41 display a version from within the context of a project, while 'outofcontext"' |
|
42 will be called in other cases. In our example, the 'incontext' view of the |
|
43 version would be something like '0.1.2', while the 'outofcontext' view would |
|
44 include the project name, e.g. 'baz 0.1.2' (since only a version number without |
|
45 the associated project doesn't make sense if you don't know yet that you're |
|
46 talking about the famous 'baz' project. |cubicweb| tries to make guess and call |
|
47 'incontext'/'outofcontext' nicely. When it can't know, the 'oneline' view should |
|
48 be used. |
|
49 |
|
50 |
|
51 List entity views |
|
52 ````````````````` |
|
53 |
|
54 .. autoclass:: ListView |
|
55 .. autoclass:: SimpleListView |
|
56 .. autoclass:: SameETypeListView |
|
57 .. autoclass:: CSVView |
|
58 |
|
59 Those list views can be given a 'subvid' arguments, telling the view to use of |
|
60 each item in the list. When not specified, the value of the 'redirect_vid' |
|
61 attribute of :class:`ListItemView` (for 'listview') or of |
|
62 :class:`SimpleListView` will be used. This default to 'outofcontext' for 'list' |
|
63 / 'incontext' for 'simplelist' |
|
64 |
|
65 |
|
66 Text entity views |
|
67 ~~~~~~~~~~~~~~~~~ |
|
68 |
|
69 Basic HTML view have some variants to be used when generating raw text, not HTML |
|
70 (for notifications for instance). Also, as explained above, some of the HTML |
|
71 views use those text views as a basis. |
|
72 |
|
73 .. autoclass:: TextView |
|
74 .. autoclass:: InContextTextView |
|
75 .. autoclass:: OutOfContextView |
|
76 """ |
|
77 |
|
78 __docformat__ = "restructuredtext en" |
|
79 from cubicweb import _ |
|
80 |
|
81 from datetime import timedelta |
|
82 from warnings import warn |
|
83 |
|
84 from six.moves import range |
|
85 |
|
86 from rql import nodes |
|
87 |
|
88 from logilab.mtconverter import TransformError, xml_escape |
|
89 from logilab.common.registry import yes |
|
90 |
|
91 from cubicweb import NoSelectableObject, tags |
|
92 from cubicweb.predicates import empty_rset, one_etype_rset, match_kwargs |
|
93 from cubicweb.schema import display_name |
|
94 from cubicweb.view import EntityView, AnyRsetView, View |
|
95 from cubicweb.uilib import cut |
|
96 from cubicweb.web.views import calendar |
|
97 |
|
98 |
|
99 class NullView(AnyRsetView): |
|
100 """:__regid__: *null* |
|
101 |
|
102 This view is the default view used when nothing needs to be rendered. It is |
|
103 always applicable and is usually used as fallback view when calling |
|
104 :meth:`_cw.view` to display nothing if the result set is empty. |
|
105 """ |
|
106 __regid__ = 'null' |
|
107 __select__ = yes() |
|
108 def call(self, **kwargs): |
|
109 pass |
|
110 cell_call = call |
|
111 |
|
112 |
|
113 class NoResultView(View): |
|
114 """:__regid__: *noresult* |
|
115 |
|
116 This view is the default view to be used when no result has been found |
|
117 (i.e. empty result set). |
|
118 |
|
119 It's usually used as fallback view when calling :meth:`_cw.view` to display |
|
120 "no results" if the result set is empty. |
|
121 """ |
|
122 __regid__ = 'noresult' |
|
123 __select__ = empty_rset() |
|
124 |
|
125 def call(self, **kwargs): |
|
126 self.w(u'<div class="searchMessage"><strong>%s</strong></div>\n' |
|
127 % self._cw._('No result matching query')) |
|
128 |
|
129 |
|
130 class FinalView(AnyRsetView): |
|
131 """:__regid__: *final* |
|
132 |
|
133 Display the value of a result set cell with minimal transformations |
|
134 (i.e. you'll get a number for entities). It is applicable on any result set, |
|
135 though usually dedicated for cells containing an attribute's value. |
|
136 """ |
|
137 __regid__ = 'final' |
|
138 |
|
139 def cell_call(self, row, col, props=None, format='text/html'): |
|
140 value = self.cw_rset.rows[row][col] |
|
141 if value is None: |
|
142 self.w(u'') |
|
143 return |
|
144 etype = self.cw_rset.description[row][col] |
|
145 if etype == 'String': |
|
146 entity, rtype = self.cw_rset.related_entity(row, col) |
|
147 if entity is not None: |
|
148 # call entity's printable_value which may have more information |
|
149 # about string format & all |
|
150 self.w(entity.printable_value(rtype, value, format=format)) |
|
151 return |
|
152 value = self._cw.printable_value(etype, value, props) |
|
153 if etype in ('Time', 'Interval'): |
|
154 self.w(value.replace(' ', ' ')) |
|
155 else: |
|
156 self.wdata(value) |
|
157 |
|
158 |
|
159 class InContextView(EntityView): |
|
160 """:__regid__: *incontext* |
|
161 |
|
162 This view is used when the entity should be considered as displayed in its |
|
163 context. By default it produces the result of ``entity.dc_title()`` wrapped in a |
|
164 link leading to the primary view of the entity. |
|
165 """ |
|
166 __regid__ = 'incontext' |
|
167 |
|
168 def cell_call(self, row, col): |
|
169 entity = self.cw_rset.get_entity(row, col) |
|
170 desc = cut(entity.dc_description(), 50) |
|
171 self.w(u'<a href="%s" title="%s">%s</a>' % ( |
|
172 xml_escape(entity.absolute_url()), xml_escape(desc), |
|
173 xml_escape(entity.dc_title()))) |
|
174 |
|
175 class OutOfContextView(EntityView): |
|
176 """:__regid__: *outofcontext* |
|
177 |
|
178 This view is used when the entity should be considered as displayed out of |
|
179 its context. By default it produces the result of ``entity.dc_long_title()`` |
|
180 wrapped in a link leading to the primary view of the entity. |
|
181 """ |
|
182 __regid__ = 'outofcontext' |
|
183 |
|
184 def cell_call(self, row, col): |
|
185 entity = self.cw_rset.get_entity(row, col) |
|
186 desc = cut(entity.dc_description(), 50) |
|
187 self.w(u'<a href="%s" title="%s">%s</a>' % ( |
|
188 xml_escape(entity.absolute_url()), xml_escape(desc), |
|
189 xml_escape(entity.dc_long_title()))) |
|
190 |
|
191 |
|
192 class OneLineView(EntityView): |
|
193 """:__regid__: *oneline* |
|
194 |
|
195 This view is used when we can't tell if the entity should be considered as |
|
196 displayed in or out of context. By default it produces the result of the |
|
197 `text` view in a link leading to the primary view of the entity. |
|
198 """ |
|
199 __regid__ = 'oneline' |
|
200 title = _('oneline') |
|
201 |
|
202 def cell_call(self, row, col, **kwargs): |
|
203 """the one line view for an entity: linked text view |
|
204 """ |
|
205 entity = self.cw_rset.get_entity(row, col) |
|
206 desc = cut(entity.dc_description(), 50) |
|
207 title = cut(entity.dc_title(), |
|
208 self._cw.property_value('navigation.short-line-size')) |
|
209 self.w(u'<a href="%s" title="%s">%s</a>' % ( |
|
210 xml_escape(entity.absolute_url()), xml_escape(desc), |
|
211 xml_escape(title))) |
|
212 |
|
213 |
|
214 # text views ################################################################### |
|
215 |
|
216 class TextView(EntityView): |
|
217 """:__regid__: *text* |
|
218 |
|
219 This is the simplest text view for an entity. By default it returns the |
|
220 result of the entity's `dc_title()` method, which is cut to fit the |
|
221 `navigation.short-line-size` property if necessary. |
|
222 """ |
|
223 __regid__ = 'text' |
|
224 title = _('text') |
|
225 content_type = 'text/plain' |
|
226 |
|
227 def call(self, **kwargs): |
|
228 """The view is called for an entire result set, by default loop other |
|
229 rows of the result set and call the same view on the particular row. |
|
230 |
|
231 Subclasses views that are applicable on None result sets will have to |
|
232 override this method. |
|
233 """ |
|
234 rset = self.cw_rset |
|
235 if rset is None: |
|
236 raise NotImplementedError(self) |
|
237 for i in range(len(rset)): |
|
238 self.wview(self.__regid__, rset, row=i, **kwargs) |
|
239 if len(rset) > 1: |
|
240 self.w(u"\n") |
|
241 |
|
242 def cell_call(self, row, col=0, **kwargs): |
|
243 entity = self.cw_rset.get_entity(row, col) |
|
244 self.w(cut(entity.dc_title(), |
|
245 self._cw.property_value('navigation.short-line-size'))) |
|
246 |
|
247 |
|
248 class InContextTextView(TextView): |
|
249 """:__regid__: *textincontext* |
|
250 |
|
251 Similar to the `text` view, but called when an entity is considered in |
|
252 context (see description of incontext HTML view for more information on |
|
253 this). By default it displays what's returned by the `dc_title()` method of |
|
254 the entity. |
|
255 """ |
|
256 __regid__ = 'textincontext' |
|
257 title = None # not listed as a possible view |
|
258 def cell_call(self, row, col): |
|
259 entity = self.cw_rset.get_entity(row, col) |
|
260 self.w(entity.dc_title()) |
|
261 |
|
262 |
|
263 class OutOfContextTextView(InContextTextView): |
|
264 """:__regid__: *textoutofcontext* |
|
265 |
|
266 Similar to the `text` view, but called when an entity is considered out of |
|
267 context (see description of outofcontext HTML view for more information on |
|
268 this). By default it displays what's returned by the `dc_long_title()` |
|
269 method of the entity. |
|
270 """ |
|
271 __regid__ = 'textoutofcontext' |
|
272 |
|
273 def cell_call(self, row, col): |
|
274 entity = self.cw_rset.get_entity(row, col) |
|
275 self.w(entity.dc_long_title()) |
|
276 |
|
277 |
|
278 # list views ################################################################## |
|
279 |
|
280 class ListView(EntityView): |
|
281 """:__regid__: *list* |
|
282 |
|
283 This view displays a list of entities by creating a HTML list (`<ul>`) and |
|
284 call the view `listitem` for each entity of the result set. The 'list' view |
|
285 will generate HTML like: |
|
286 |
|
287 .. sourcecode:: html |
|
288 |
|
289 <ul class="section"> |
|
290 <li>"result of 'subvid' view for a row</li> |
|
291 ... |
|
292 </ul> |
|
293 |
|
294 If you wish to use a different view for each entity, either subclass and |
|
295 change the :attr:`item_vid` class attribute or specify a `subvid` argument |
|
296 when calling this view. |
|
297 """ |
|
298 __regid__ = 'list' |
|
299 title = _('list') |
|
300 item_vid = 'listitem' |
|
301 |
|
302 def call(self, klass=None, title=None, subvid=None, listid=None, **kwargs): |
|
303 """display a list of entities by calling their <item_vid> view |
|
304 |
|
305 :param listid: the DOM id to use for the root element |
|
306 """ |
|
307 # XXX much of the behaviour here should probably be outside this view |
|
308 if subvid is None and 'subvid' in self._cw.form: |
|
309 subvid = self._cw.form.pop('subvid') # consume it |
|
310 if listid: |
|
311 listid = u' id="%s"' % listid |
|
312 else: |
|
313 listid = u'' |
|
314 if title: |
|
315 self.w(u'<div%s class="%s"><h4>%s</h4>\n' % (listid, klass or 'section', title)) |
|
316 self.w(u'<ul>\n') |
|
317 else: |
|
318 self.w(u'<ul%s class="%s">\n' % (listid, klass or 'section')) |
|
319 for i in range(self.cw_rset.rowcount): |
|
320 self.cell_call(row=i, col=0, vid=subvid, klass=klass, **kwargs) |
|
321 self.w(u'</ul>\n') |
|
322 if title: |
|
323 self.w(u'</div>\n') |
|
324 |
|
325 def cell_call(self, row, col=0, vid=None, klass=None, **kwargs): |
|
326 self.w(u'<li>') |
|
327 self.wview(self.item_vid, self.cw_rset, row=row, col=col, vid=vid, **kwargs) |
|
328 self.w(u'</li>\n') |
|
329 |
|
330 |
|
331 class ListItemView(EntityView): |
|
332 __regid__ = 'listitem' |
|
333 |
|
334 @property |
|
335 def redirect_vid(self): |
|
336 if self._cw.search_state[0] == 'normal': |
|
337 return 'outofcontext' |
|
338 return 'outofcontext-search' |
|
339 |
|
340 def cell_call(self, row, col, vid=None, **kwargs): |
|
341 if not vid: |
|
342 vid = self.redirect_vid |
|
343 try: |
|
344 self.wview(vid, self.cw_rset, row=row, col=col, **kwargs) |
|
345 except NoSelectableObject: |
|
346 if vid == self.redirect_vid: |
|
347 raise |
|
348 self.wview(self.redirect_vid, self.cw_rset, row=row, col=col, **kwargs) |
|
349 |
|
350 |
|
351 class SimpleListView(ListItemView): |
|
352 """:__regid__: *simplelist* |
|
353 |
|
354 Similar to :class:~cubicweb.web.views.baseviews.ListView but using '<div>' |
|
355 instead of '<ul>'. It rely on '<div>' behaviour to separate items. HTML will |
|
356 look like |
|
357 |
|
358 .. sourcecode:: html |
|
359 |
|
360 <div class="section">"result of 'subvid' view for a row</div> |
|
361 ... |
|
362 |
|
363 |
|
364 It relies on base :class:`~cubicweb.view.View` class implementation of the |
|
365 :meth:`call` method to insert those <div>. |
|
366 """ |
|
367 __regid__ = 'simplelist' |
|
368 redirect_vid = 'incontext' |
|
369 |
|
370 def call(self, subvid=None, **kwargs): |
|
371 """display a list of entities by calling their <item_vid> view |
|
372 |
|
373 :param listid: the DOM id to use for the root element |
|
374 """ |
|
375 if subvid is None and 'vid' in kwargs: |
|
376 warn("should give a 'subvid' argument instead of 'vid'", |
|
377 DeprecationWarning, stacklevel=2) |
|
378 else: |
|
379 kwargs['vid'] = subvid |
|
380 return super(SimpleListView, self).call(**kwargs) |
|
381 |
|
382 |
|
383 class SameETypeListView(EntityView): |
|
384 """:__regid__: *sameetypelist* |
|
385 |
|
386 This view displays a list of entities of the same type, in HTML section |
|
387 ('<div>') and call the view `sameetypelistitem` for each entity of the |
|
388 result set. It's designed to get a more adapted global list when displayed |
|
389 entities are all of the same type (for instance, display gallery if there |
|
390 are only images entities). |
|
391 """ |
|
392 __regid__ = 'sameetypelist' |
|
393 __select__ = EntityView.__select__ & one_etype_rset() |
|
394 item_vid = 'sameetypelistitem' |
|
395 |
|
396 @property |
|
397 def title(self): |
|
398 etype = next(iter(self.cw_rset.column_types(0))) |
|
399 return display_name(self._cw, etype, form='plural') |
|
400 |
|
401 def call(self, **kwargs): |
|
402 """display a list of entities by calling their <item_vid> view""" |
|
403 showtitle = kwargs.pop('showtitle', not 'vtitle' in self._cw.form) |
|
404 if showtitle: |
|
405 self.w(u'<h1>%s</h1>' % self.title) |
|
406 super(SameETypeListView, self).call(**kwargs) |
|
407 |
|
408 def cell_call(self, row, col=0, **kwargs): |
|
409 self.wview(self.item_vid, self.cw_rset, row=row, col=col, **kwargs) |
|
410 |
|
411 |
|
412 class SameETypeListItemView(EntityView): |
|
413 __regid__ = 'sameetypelistitem' |
|
414 |
|
415 def cell_call(self, row, col, **kwargs): |
|
416 self.wview('listitem', self.cw_rset, row=row, col=col, **kwargs) |
|
417 |
|
418 |
|
419 class CSVView(SimpleListView): |
|
420 """:__regid__: *csv* |
|
421 |
|
422 This view displays each entity in a coma separated list. It is NOT related |
|
423 to the well-known text file format. |
|
424 """ |
|
425 __regid__ = 'csv' |
|
426 redirect_vid = 'incontext' |
|
427 separator = u', ' |
|
428 |
|
429 def call(self, subvid=None, **kwargs): |
|
430 kwargs['vid'] = subvid |
|
431 rset = self.cw_rset |
|
432 for i in range(len(rset)): |
|
433 self.cell_call(i, 0, **kwargs) |
|
434 if i < rset.rowcount-1: |
|
435 self.w(self.separator) |
|
436 |
|
437 |
|
438 # XXX to be documented views ################################################### |
|
439 |
|
440 class MetaDataView(EntityView): |
|
441 """paragraph view of some metadata""" |
|
442 __regid__ = 'metadata' |
|
443 show_eid = True |
|
444 |
|
445 def cell_call(self, row, col): |
|
446 _ = self._cw._ |
|
447 entity = self.cw_rset.get_entity(row, col) |
|
448 self.w(u'<div>') |
|
449 if self.show_eid: |
|
450 self.w(u'%s #%s - ' % (entity.dc_type(), entity.eid)) |
|
451 if entity.modification_date != entity.creation_date: |
|
452 self.w(u'<span>%s</span> ' % _('latest update on')) |
|
453 self.w(u'<span class="value">%s</span>, ' |
|
454 % self._cw.format_date(entity.modification_date)) |
|
455 # entities from external source may not have a creation date (eg ldap) |
|
456 if entity.creation_date: |
|
457 self.w(u'<span>%s</span> ' % _('created on')) |
|
458 self.w(u'<span class="value">%s</span>' |
|
459 % self._cw.format_date(entity.creation_date)) |
|
460 if entity.creator: |
|
461 if entity.creation_date: |
|
462 self.w(u' <span>%s</span> ' % _('by')) |
|
463 else: |
|
464 self.w(u' <span>%s</span> ' % _('created_by')) |
|
465 self.w(u'<span class="value">%s</span>' % entity.creator.name()) |
|
466 meta = entity.cw_metainformation() |
|
467 if meta['source']['uri'] != 'system': |
|
468 self.w(u' (<span>%s</span>' % _('cw_source')) |
|
469 self.w(u' <span class="value">%s</span>)' % meta['source']['uri']) |
|
470 self.w(u'</div>') |
|
471 |
|
472 |
|
473 class TreeItemView(ListItemView): |
|
474 __regid__ = 'treeitem' |
|
475 |
|
476 def cell_call(self, row, col): |
|
477 self.wview('incontext', self.cw_rset, row=row, col=col) |
|
478 |
|
479 |
|
480 class TextSearchResultView(EntityView): |
|
481 """this view is used to display full-text search |
|
482 |
|
483 It tries to highlight part of data where the search word appears. |
|
484 |
|
485 XXX: finish me (fixed line width, fixed number of lines, CSS, etc.) |
|
486 """ |
|
487 __regid__ = 'tsearch' |
|
488 |
|
489 def cell_call(self, row, col, **kwargs): |
|
490 entity = self.cw_rset.complete_entity(row, col) |
|
491 self.w(entity.view('incontext')) |
|
492 searched = self.cw_rset.searched_text() |
|
493 if searched is None: |
|
494 return |
|
495 searched = searched.lower() |
|
496 highlighted = '<b>%s</b>' % searched |
|
497 for attr in entity.e_schema.indexable_attributes(): |
|
498 try: |
|
499 value = xml_escape(entity.printable_value(attr, format='text/plain').lower()) |
|
500 except TransformError as ex: |
|
501 continue |
|
502 except Exception: |
|
503 continue |
|
504 if searched in value: |
|
505 contexts = [] |
|
506 for ctx in value.split(searched): |
|
507 if len(ctx) > 30: |
|
508 contexts.append(u'...' + ctx[-30:]) |
|
509 else: |
|
510 contexts.append(ctx) |
|
511 value = u'\n' + highlighted.join(contexts) |
|
512 self.w(value.replace('\n', '<br/>')) |
|
513 |
|
514 |
|
515 class TooltipView(EntityView): |
|
516 """A entity view used in a tooltip""" |
|
517 __regid__ = 'tooltip' |
|
518 def cell_call(self, row, col): |
|
519 self.wview('oneline', self.cw_rset, row=row, col=col) |
|
520 |
|
521 |
|
522 class GroupByView(EntityView): |
|
523 """grouped view of a result set. The `group_key` method return the group |
|
524 key of an entities (a string or tuple of string). |
|
525 |
|
526 For each group, display a link to entities of this group by generating url |
|
527 like <basepath>/<key> or <basepath>/<key item 1>/<key item 2>. |
|
528 """ |
|
529 __abstract__ = True |
|
530 __select__ = EntityView.__select__ & match_kwargs('basepath') |
|
531 entity_attribute = None |
|
532 reversed = False |
|
533 |
|
534 def index_url(self, basepath, key, **kwargs): |
|
535 if isinstance(key, (list, tuple)): |
|
536 key = '/'.join(key) |
|
537 return self._cw.build_url('%s/%s' % (basepath, key), |
|
538 **kwargs) |
|
539 |
|
540 def index_link(self, basepath, key, items): |
|
541 url = self.index_url(basepath, key) |
|
542 if isinstance(key, (list, tuple)): |
|
543 key = ' '.join(key) |
|
544 return tags.a(key, href=url) |
|
545 |
|
546 def group_key(self, entity, **kwargs): |
|
547 value = getattr(entity, self.entity_attribute) |
|
548 if callable(value): |
|
549 value = value() |
|
550 return value |
|
551 |
|
552 def call(self, basepath, maxentries=None, **kwargs): |
|
553 index = {} |
|
554 for entity in self.cw_rset.entities(): |
|
555 index.setdefault(self.group_key(entity, **kwargs), []).append(entity) |
|
556 displayed = sorted(index) |
|
557 if self.reversed: |
|
558 displayed = reversed(displayed) |
|
559 if maxentries is None: |
|
560 needmore = False |
|
561 else: |
|
562 needmore = len(index) > maxentries |
|
563 displayed = tuple(displayed)[:maxentries] |
|
564 w = self.w |
|
565 w(u'<ul class="boxListing">') |
|
566 for key in displayed: |
|
567 if key: |
|
568 w(u'<li>%s</li>\n' % |
|
569 self.index_link(basepath, key, index[key])) |
|
570 if needmore: |
|
571 url = self._cw.build_url('view', vid=self.__regid__, |
|
572 rql=self.cw_rset.printable_rql()) |
|
573 w( u'<li>%s</li>\n' % tags.a(u'[%s]' % self._cw._('see more'), |
|
574 href=url)) |
|
575 w(u'</ul>\n') |
|
576 |
|
577 |
|
578 class ArchiveView(GroupByView): |
|
579 """archive view of a result set. Links to months are built using a basepath |
|
580 parameters, eg using url like <basepath>/<year>/<month> |
|
581 """ |
|
582 __regid__ = 'cw.archive.by_date' |
|
583 entity_attribute = 'creation_date' |
|
584 reversed = True |
|
585 |
|
586 def group_key(self, entity, **kwargs): |
|
587 value = super(ArchiveView, self).group_key(entity, **kwargs) |
|
588 return '%04d' % value.year, '%02d' % value.month |
|
589 |
|
590 def index_link(self, basepath, key, items): |
|
591 """represent a single month entry""" |
|
592 year, month = key |
|
593 label = u'%s %s [%s]' % (self._cw._(calendar.MONTHNAMES[int(month)-1]), |
|
594 year, len(items)) |
|
595 etypes = set(entity.cw_etype for entity in items) |
|
596 vtitle = '%s %s' % (', '.join(display_name(self._cw, etype, 'plural') |
|
597 for etype in etypes), |
|
598 label) |
|
599 title = self._cw._('archive for %(month)s/%(year)s') % { |
|
600 'month': month, 'year': year} |
|
601 url = self.index_url(basepath, key, vtitle=vtitle) |
|
602 return tags.a(label, href=url, title=title) |
|
603 |
|
604 |
|
605 class AuthorView(GroupByView): |
|
606 """author view of a result set. Links to month are built using a basepath |
|
607 parameters, eg using url like <basepath>/<author> |
|
608 """ |
|
609 __regid__ = 'cw.archive.by_author' |
|
610 entity_attribute = 'creator' |
|
611 |
|
612 def group_key(self, entity, **kwargs): |
|
613 value = super(AuthorView, self).group_key(entity, **kwargs) |
|
614 if value: |
|
615 return (value.name(), value.login) |
|
616 return (None, None) |
|
617 |
|
618 def index_link(self, basepath, key, items): |
|
619 if key[0] is None: |
|
620 return |
|
621 label = u'%s [%s]' % (key[0], len(items)) |
|
622 etypes = set(entity.cw_etype for entity in items) |
|
623 vtitle = self._cw._('%(etype)s by %(author)s') % { |
|
624 'etype': ', '.join(display_name(self._cw, etype, 'plural') |
|
625 for etype in etypes), |
|
626 'author': label} |
|
627 url = self.index_url(basepath, key[1], vtitle=vtitle) |
|
628 title = self._cw._('archive for %(author)s') % {'author': key[0]} |
|
629 return tags.a(label, href=url, title=title) |
|
630 |
|
631 |
|
632 # bw compat #################################################################### |
|
633 |
|
634 from logilab.common.deprecation import class_moved, class_deprecated |
|
635 |
|
636 from cubicweb.web.views import boxes, xmlrss, primary, tableview |
|
637 PrimaryView = class_moved(primary.PrimaryView) |
|
638 SideBoxView = class_moved(boxes.SideBoxView) |
|
639 XmlView = class_moved(xmlrss.XMLView) |
|
640 XmlItemView = class_moved(xmlrss.XMLItemView) |
|
641 XmlRsetView = class_moved(xmlrss.XMLRsetView) |
|
642 RssView = class_moved(xmlrss.RSSView) |
|
643 RssItemView = class_moved(xmlrss.RSSItemView) |
|
644 TableView = class_moved(tableview.TableView) |
|