web/views/baseviews.py
changeset 11057 0b59724cb3f2
parent 11052 058bb3dc685f
child 11058 23eb30449fe5
--- 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(' ', '&#160;'))
-        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)