--- a/web/views/baseviews.py Mon Jan 04 18:40:30 2016 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,644 +0,0 @@
-# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
-#
-# This file is part of CubicWeb.
-#
-# CubicWeb is free software: you can redistribute it and/or modify it under the
-# terms of the GNU Lesser General Public License as published by the Free
-# Software Foundation, either version 2.1 of the License, or (at your option)
-# any later version.
-#
-# CubicWeb is distributed in the hope that it will be useful, but WITHOUT
-# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
-# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
-# details.
-#
-# You should have received a copy of the GNU Lesser General Public License along
-# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
-"""
-HTML views
-~~~~~~~~~~
-
-Special views
-`````````````
-
-.. autoclass:: NullView
-.. autoclass:: NoResultView
-.. autoclass:: FinalView
-
-
-Base entity views
-`````````````````
-
-.. autoclass:: InContextView
-.. autoclass:: OutOfContextView
-.. autoclass:: OneLineView
-
-Those are used to display a link to an entity, whose label depends on the entity
-having to be displayed in or out of context (of another entity): some entities
-make sense in the context of another entity. For instance, the `Version` of a
-`Project` in forge. So one may expect that 'incontext' will be called when
-display a version from within the context of a project, while 'outofcontext"'
-will be called in other cases. In our example, the 'incontext' view of the
-version would be something like '0.1.2', while the 'outofcontext' view would
-include the project name, e.g. 'baz 0.1.2' (since only a version number without
-the associated project doesn't make sense if you don't know yet that you're
-talking about the famous 'baz' project. |cubicweb| tries to make guess and call
-'incontext'/'outofcontext' nicely. When it can't know, the 'oneline' view should
-be used.
-
-
-List entity views
-`````````````````
-
-.. autoclass:: ListView
-.. autoclass:: SimpleListView
-.. autoclass:: SameETypeListView
-.. autoclass:: CSVView
-
-Those list views can be given a 'subvid' arguments, telling the view to use of
-each item in the list. When not specified, the value of the 'redirect_vid'
-attribute of :class:`ListItemView` (for 'listview') or of
-:class:`SimpleListView` will be used. This default to 'outofcontext' for 'list'
-/ 'incontext' for 'simplelist'
-
-
-Text entity views
-~~~~~~~~~~~~~~~~~
-
-Basic HTML view have some variants to be used when generating raw text, not HTML
-(for notifications for instance). Also, as explained above, some of the HTML
-views use those text views as a basis.
-
-.. autoclass:: TextView
-.. autoclass:: InContextTextView
-.. autoclass:: OutOfContextView
-"""
-
-__docformat__ = "restructuredtext en"
-from cubicweb import _
-
-from datetime import timedelta
-from warnings import warn
-
-from six.moves import range
-
-from rql import nodes
-
-from logilab.mtconverter import TransformError, xml_escape
-from logilab.common.registry import yes
-
-from cubicweb import NoSelectableObject, tags
-from cubicweb.predicates import empty_rset, one_etype_rset, match_kwargs
-from cubicweb.schema import display_name
-from cubicweb.view import EntityView, AnyRsetView, View
-from cubicweb.uilib import cut
-from cubicweb.web.views import calendar
-
-
-class NullView(AnyRsetView):
- """:__regid__: *null*
-
- This view is the default view used when nothing needs to be rendered. It is
- always applicable and is usually used as fallback view when calling
- :meth:`_cw.view` to display nothing if the result set is empty.
- """
- __regid__ = 'null'
- __select__ = yes()
- def call(self, **kwargs):
- pass
- cell_call = call
-
-
-class NoResultView(View):
- """:__regid__: *noresult*
-
- This view is the default view to be used when no result has been found
- (i.e. empty result set).
-
- It's usually used as fallback view when calling :meth:`_cw.view` to display
- "no results" if the result set is empty.
- """
- __regid__ = 'noresult'
- __select__ = empty_rset()
-
- def call(self, **kwargs):
- self.w(u'<div class="searchMessage"><strong>%s</strong></div>\n'
- % self._cw._('No result matching query'))
-
-
-class FinalView(AnyRsetView):
- """:__regid__: *final*
-
- Display the value of a result set cell with minimal transformations
- (i.e. you'll get a number for entities). It is applicable on any result set,
- though usually dedicated for cells containing an attribute's value.
- """
- __regid__ = 'final'
-
- def cell_call(self, row, col, props=None, format='text/html'):
- value = self.cw_rset.rows[row][col]
- if value is None:
- self.w(u'')
- return
- etype = self.cw_rset.description[row][col]
- if etype == 'String':
- entity, rtype = self.cw_rset.related_entity(row, col)
- if entity is not None:
- # call entity's printable_value which may have more information
- # about string format & all
- self.w(entity.printable_value(rtype, value, format=format))
- return
- value = self._cw.printable_value(etype, value, props)
- if etype in ('Time', 'Interval'):
- self.w(value.replace(' ', ' '))
- else:
- self.wdata(value)
-
-
-class InContextView(EntityView):
- """:__regid__: *incontext*
-
- This view is used when the entity should be considered as displayed in its
- context. By default it produces the result of ``entity.dc_title()`` wrapped in a
- link leading to the primary view of the entity.
- """
- __regid__ = 'incontext'
-
- def cell_call(self, row, col):
- entity = self.cw_rset.get_entity(row, col)
- desc = cut(entity.dc_description(), 50)
- self.w(u'<a href="%s" title="%s">%s</a>' % (
- xml_escape(entity.absolute_url()), xml_escape(desc),
- xml_escape(entity.dc_title())))
-
-class OutOfContextView(EntityView):
- """:__regid__: *outofcontext*
-
- This view is used when the entity should be considered as displayed out of
- its context. By default it produces the result of ``entity.dc_long_title()``
- wrapped in a link leading to the primary view of the entity.
- """
- __regid__ = 'outofcontext'
-
- def cell_call(self, row, col):
- entity = self.cw_rset.get_entity(row, col)
- desc = cut(entity.dc_description(), 50)
- self.w(u'<a href="%s" title="%s">%s</a>' % (
- xml_escape(entity.absolute_url()), xml_escape(desc),
- xml_escape(entity.dc_long_title())))
-
-
-class OneLineView(EntityView):
- """:__regid__: *oneline*
-
- This view is used when we can't tell if the entity should be considered as
- displayed in or out of context. By default it produces the result of the
- `text` view in a link leading to the primary view of the entity.
- """
- __regid__ = 'oneline'
- title = _('oneline')
-
- def cell_call(self, row, col, **kwargs):
- """the one line view for an entity: linked text view
- """
- entity = self.cw_rset.get_entity(row, col)
- desc = cut(entity.dc_description(), 50)
- title = cut(entity.dc_title(),
- self._cw.property_value('navigation.short-line-size'))
- self.w(u'<a href="%s" title="%s">%s</a>' % (
- xml_escape(entity.absolute_url()), xml_escape(desc),
- xml_escape(title)))
-
-
-# text views ###################################################################
-
-class TextView(EntityView):
- """:__regid__: *text*
-
- This is the simplest text view for an entity. By default it returns the
- result of the entity's `dc_title()` method, which is cut to fit the
- `navigation.short-line-size` property if necessary.
- """
- __regid__ = 'text'
- title = _('text')
- content_type = 'text/plain'
-
- def call(self, **kwargs):
- """The view is called for an entire result set, by default loop other
- rows of the result set and call the same view on the particular row.
-
- Subclasses views that are applicable on None result sets will have to
- override this method.
- """
- rset = self.cw_rset
- if rset is None:
- raise NotImplementedError(self)
- for i in range(len(rset)):
- self.wview(self.__regid__, rset, row=i, **kwargs)
- if len(rset) > 1:
- self.w(u"\n")
-
- def cell_call(self, row, col=0, **kwargs):
- entity = self.cw_rset.get_entity(row, col)
- self.w(cut(entity.dc_title(),
- self._cw.property_value('navigation.short-line-size')))
-
-
-class InContextTextView(TextView):
- """:__regid__: *textincontext*
-
- Similar to the `text` view, but called when an entity is considered in
- context (see description of incontext HTML view for more information on
- this). By default it displays what's returned by the `dc_title()` method of
- the entity.
- """
- __regid__ = 'textincontext'
- title = None # not listed as a possible view
- def cell_call(self, row, col):
- entity = self.cw_rset.get_entity(row, col)
- self.w(entity.dc_title())
-
-
-class OutOfContextTextView(InContextTextView):
- """:__regid__: *textoutofcontext*
-
- Similar to the `text` view, but called when an entity is considered out of
- context (see description of outofcontext HTML view for more information on
- this). By default it displays what's returned by the `dc_long_title()`
- method of the entity.
- """
- __regid__ = 'textoutofcontext'
-
- def cell_call(self, row, col):
- entity = self.cw_rset.get_entity(row, col)
- self.w(entity.dc_long_title())
-
-
-# list views ##################################################################
-
-class ListView(EntityView):
- """:__regid__: *list*
-
- This view displays a list of entities by creating a HTML list (`<ul>`) and
- call the view `listitem` for each entity of the result set. The 'list' view
- will generate HTML like:
-
- .. sourcecode:: html
-
- <ul class="section">
- <li>"result of 'subvid' view for a row</li>
- ...
- </ul>
-
- If you wish to use a different view for each entity, either subclass and
- change the :attr:`item_vid` class attribute or specify a `subvid` argument
- when calling this view.
- """
- __regid__ = 'list'
- title = _('list')
- item_vid = 'listitem'
-
- def call(self, klass=None, title=None, subvid=None, listid=None, **kwargs):
- """display a list of entities by calling their <item_vid> view
-
- :param listid: the DOM id to use for the root element
- """
- # XXX much of the behaviour here should probably be outside this view
- if subvid is None and 'subvid' in self._cw.form:
- subvid = self._cw.form.pop('subvid') # consume it
- if listid:
- listid = u' id="%s"' % listid
- else:
- listid = u''
- if title:
- self.w(u'<div%s class="%s"><h4>%s</h4>\n' % (listid, klass or 'section', title))
- self.w(u'<ul>\n')
- else:
- self.w(u'<ul%s class="%s">\n' % (listid, klass or 'section'))
- for i in range(self.cw_rset.rowcount):
- self.cell_call(row=i, col=0, vid=subvid, klass=klass, **kwargs)
- self.w(u'</ul>\n')
- if title:
- self.w(u'</div>\n')
-
- def cell_call(self, row, col=0, vid=None, klass=None, **kwargs):
- self.w(u'<li>')
- self.wview(self.item_vid, self.cw_rset, row=row, col=col, vid=vid, **kwargs)
- self.w(u'</li>\n')
-
-
-class ListItemView(EntityView):
- __regid__ = 'listitem'
-
- @property
- def redirect_vid(self):
- if self._cw.search_state[0] == 'normal':
- return 'outofcontext'
- return 'outofcontext-search'
-
- def cell_call(self, row, col, vid=None, **kwargs):
- if not vid:
- vid = self.redirect_vid
- try:
- self.wview(vid, self.cw_rset, row=row, col=col, **kwargs)
- except NoSelectableObject:
- if vid == self.redirect_vid:
- raise
- self.wview(self.redirect_vid, self.cw_rset, row=row, col=col, **kwargs)
-
-
-class SimpleListView(ListItemView):
- """:__regid__: *simplelist*
-
- Similar to :class:~cubicweb.web.views.baseviews.ListView but using '<div>'
- instead of '<ul>'. It rely on '<div>' behaviour to separate items. HTML will
- look like
-
- .. sourcecode:: html
-
- <div class="section">"result of 'subvid' view for a row</div>
- ...
-
-
- It relies on base :class:`~cubicweb.view.View` class implementation of the
- :meth:`call` method to insert those <div>.
- """
- __regid__ = 'simplelist'
- redirect_vid = 'incontext'
-
- def call(self, subvid=None, **kwargs):
- """display a list of entities by calling their <item_vid> view
-
- :param listid: the DOM id to use for the root element
- """
- if subvid is None and 'vid' in kwargs:
- warn("should give a 'subvid' argument instead of 'vid'",
- DeprecationWarning, stacklevel=2)
- else:
- kwargs['vid'] = subvid
- return super(SimpleListView, self).call(**kwargs)
-
-
-class SameETypeListView(EntityView):
- """:__regid__: *sameetypelist*
-
- This view displays a list of entities of the same type, in HTML section
- ('<div>') and call the view `sameetypelistitem` for each entity of the
- result set. It's designed to get a more adapted global list when displayed
- entities are all of the same type (for instance, display gallery if there
- are only images entities).
- """
- __regid__ = 'sameetypelist'
- __select__ = EntityView.__select__ & one_etype_rset()
- item_vid = 'sameetypelistitem'
-
- @property
- def title(self):
- etype = next(iter(self.cw_rset.column_types(0)))
- return display_name(self._cw, etype, form='plural')
-
- def call(self, **kwargs):
- """display a list of entities by calling their <item_vid> view"""
- showtitle = kwargs.pop('showtitle', not 'vtitle' in self._cw.form)
- if showtitle:
- self.w(u'<h1>%s</h1>' % self.title)
- super(SameETypeListView, self).call(**kwargs)
-
- def cell_call(self, row, col=0, **kwargs):
- self.wview(self.item_vid, self.cw_rset, row=row, col=col, **kwargs)
-
-
-class SameETypeListItemView(EntityView):
- __regid__ = 'sameetypelistitem'
-
- def cell_call(self, row, col, **kwargs):
- self.wview('listitem', self.cw_rset, row=row, col=col, **kwargs)
-
-
-class CSVView(SimpleListView):
- """:__regid__: *csv*
-
- This view displays each entity in a coma separated list. It is NOT related
- to the well-known text file format.
- """
- __regid__ = 'csv'
- redirect_vid = 'incontext'
- separator = u', '
-
- def call(self, subvid=None, **kwargs):
- kwargs['vid'] = subvid
- rset = self.cw_rset
- for i in range(len(rset)):
- self.cell_call(i, 0, **kwargs)
- if i < rset.rowcount-1:
- self.w(self.separator)
-
-
-# XXX to be documented views ###################################################
-
-class MetaDataView(EntityView):
- """paragraph view of some metadata"""
- __regid__ = 'metadata'
- show_eid = True
-
- def cell_call(self, row, col):
- _ = self._cw._
- entity = self.cw_rset.get_entity(row, col)
- self.w(u'<div>')
- if self.show_eid:
- self.w(u'%s #%s - ' % (entity.dc_type(), entity.eid))
- if entity.modification_date != entity.creation_date:
- self.w(u'<span>%s</span> ' % _('latest update on'))
- self.w(u'<span class="value">%s</span>, '
- % self._cw.format_date(entity.modification_date))
- # entities from external source may not have a creation date (eg ldap)
- if entity.creation_date:
- self.w(u'<span>%s</span> ' % _('created on'))
- self.w(u'<span class="value">%s</span>'
- % self._cw.format_date(entity.creation_date))
- if entity.creator:
- if entity.creation_date:
- self.w(u' <span>%s</span> ' % _('by'))
- else:
- self.w(u' <span>%s</span> ' % _('created_by'))
- self.w(u'<span class="value">%s</span>' % entity.creator.name())
- meta = entity.cw_metainformation()
- if meta['source']['uri'] != 'system':
- self.w(u' (<span>%s</span>' % _('cw_source'))
- self.w(u' <span class="value">%s</span>)' % meta['source']['uri'])
- self.w(u'</div>')
-
-
-class TreeItemView(ListItemView):
- __regid__ = 'treeitem'
-
- def cell_call(self, row, col):
- self.wview('incontext', self.cw_rset, row=row, col=col)
-
-
-class TextSearchResultView(EntityView):
- """this view is used to display full-text search
-
- It tries to highlight part of data where the search word appears.
-
- XXX: finish me (fixed line width, fixed number of lines, CSS, etc.)
- """
- __regid__ = 'tsearch'
-
- def cell_call(self, row, col, **kwargs):
- entity = self.cw_rset.complete_entity(row, col)
- self.w(entity.view('incontext'))
- searched = self.cw_rset.searched_text()
- if searched is None:
- return
- searched = searched.lower()
- highlighted = '<b>%s</b>' % searched
- for attr in entity.e_schema.indexable_attributes():
- try:
- value = xml_escape(entity.printable_value(attr, format='text/plain').lower())
- except TransformError as ex:
- continue
- except Exception:
- continue
- if searched in value:
- contexts = []
- for ctx in value.split(searched):
- if len(ctx) > 30:
- contexts.append(u'...' + ctx[-30:])
- else:
- contexts.append(ctx)
- value = u'\n' + highlighted.join(contexts)
- self.w(value.replace('\n', '<br/>'))
-
-
-class TooltipView(EntityView):
- """A entity view used in a tooltip"""
- __regid__ = 'tooltip'
- def cell_call(self, row, col):
- self.wview('oneline', self.cw_rset, row=row, col=col)
-
-
-class GroupByView(EntityView):
- """grouped view of a result set. The `group_key` method return the group
- key of an entities (a string or tuple of string).
-
- For each group, display a link to entities of this group by generating url
- like <basepath>/<key> or <basepath>/<key item 1>/<key item 2>.
- """
- __abstract__ = True
- __select__ = EntityView.__select__ & match_kwargs('basepath')
- entity_attribute = None
- reversed = False
-
- def index_url(self, basepath, key, **kwargs):
- if isinstance(key, (list, tuple)):
- key = '/'.join(key)
- return self._cw.build_url('%s/%s' % (basepath, key),
- **kwargs)
-
- def index_link(self, basepath, key, items):
- url = self.index_url(basepath, key)
- if isinstance(key, (list, tuple)):
- key = ' '.join(key)
- return tags.a(key, href=url)
-
- def group_key(self, entity, **kwargs):
- value = getattr(entity, self.entity_attribute)
- if callable(value):
- value = value()
- return value
-
- def call(self, basepath, maxentries=None, **kwargs):
- index = {}
- for entity in self.cw_rset.entities():
- index.setdefault(self.group_key(entity, **kwargs), []).append(entity)
- displayed = sorted(index)
- if self.reversed:
- displayed = reversed(displayed)
- if maxentries is None:
- needmore = False
- else:
- needmore = len(index) > maxentries
- displayed = tuple(displayed)[:maxentries]
- w = self.w
- w(u'<ul class="boxListing">')
- for key in displayed:
- if key:
- w(u'<li>%s</li>\n' %
- self.index_link(basepath, key, index[key]))
- if needmore:
- url = self._cw.build_url('view', vid=self.__regid__,
- rql=self.cw_rset.printable_rql())
- w( u'<li>%s</li>\n' % tags.a(u'[%s]' % self._cw._('see more'),
- href=url))
- w(u'</ul>\n')
-
-
-class ArchiveView(GroupByView):
- """archive view of a result set. Links to months are built using a basepath
- parameters, eg using url like <basepath>/<year>/<month>
- """
- __regid__ = 'cw.archive.by_date'
- entity_attribute = 'creation_date'
- reversed = True
-
- def group_key(self, entity, **kwargs):
- value = super(ArchiveView, self).group_key(entity, **kwargs)
- return '%04d' % value.year, '%02d' % value.month
-
- def index_link(self, basepath, key, items):
- """represent a single month entry"""
- year, month = key
- label = u'%s %s [%s]' % (self._cw._(calendar.MONTHNAMES[int(month)-1]),
- year, len(items))
- etypes = set(entity.cw_etype for entity in items)
- vtitle = '%s %s' % (', '.join(display_name(self._cw, etype, 'plural')
- for etype in etypes),
- label)
- title = self._cw._('archive for %(month)s/%(year)s') % {
- 'month': month, 'year': year}
- url = self.index_url(basepath, key, vtitle=vtitle)
- return tags.a(label, href=url, title=title)
-
-
-class AuthorView(GroupByView):
- """author view of a result set. Links to month are built using a basepath
- parameters, eg using url like <basepath>/<author>
- """
- __regid__ = 'cw.archive.by_author'
- entity_attribute = 'creator'
-
- def group_key(self, entity, **kwargs):
- value = super(AuthorView, self).group_key(entity, **kwargs)
- if value:
- return (value.name(), value.login)
- return (None, None)
-
- def index_link(self, basepath, key, items):
- if key[0] is None:
- return
- label = u'%s [%s]' % (key[0], len(items))
- etypes = set(entity.cw_etype for entity in items)
- vtitle = self._cw._('%(etype)s by %(author)s') % {
- 'etype': ', '.join(display_name(self._cw, etype, 'plural')
- for etype in etypes),
- 'author': label}
- url = self.index_url(basepath, key[1], vtitle=vtitle)
- title = self._cw._('archive for %(author)s') % {'author': key[0]}
- return tags.a(label, href=url, title=title)
-
-
-# bw compat ####################################################################
-
-from logilab.common.deprecation import class_moved, class_deprecated
-
-from cubicweb.web.views import boxes, xmlrss, primary, tableview
-PrimaryView = class_moved(primary.PrimaryView)
-SideBoxView = class_moved(boxes.SideBoxView)
-XmlView = class_moved(xmlrss.XMLView)
-XmlItemView = class_moved(xmlrss.XMLItemView)
-XmlRsetView = class_moved(xmlrss.XMLRsetView)
-RssView = class_moved(xmlrss.RSSView)
-RssItemView = class_moved(xmlrss.RSSItemView)
-TableView = class_moved(tableview.TableView)