doc/book/devweb/views/table.rst
author Philippe Pepiot <philippe.pepiot@logilab.fr>
Thu, 05 Mar 2020 10:41:14 +0100
branch3.26
changeset 12905 fb6aecd654a3
parent 10829 550c2d27339f
permissions -rw-r--r--
[pkg] version 3.26.16

Table views
-----------

.. automodule:: cubicweb.web.views.tableview

Example
```````

Let us take an example from the timesheet cube:

.. sourcecode:: python

    class ActivityResourcesTable(EntityView):
        __regid__ = 'activity.resources.table'
        __select__ = is_instance('Activity')

        def call(self, showresource=True):
            eids = ','.join(str(row[0]) for row in self.cw_rset)
            rql = ('Any R,D,DUR,WO,DESCR,S,A, SN,RT,WT ORDERBY D DESC '
                   'WHERE '
                   '   A is Activity, A done_by R, R title RT, '
                   '   A diem D, A duration DUR, '
                   '   A done_for WO, WO title WT, '
                   '   A description DESCR, A in_state S, S name SN, '
                   '   A eid IN (%s)' % eids)
            rset = self._cw.execute(rql)
            self.wview('resource.table', rset, 'null')

    class ResourcesTable(RsetTableView):
        __regid__ = 'resource.table'
        # notice you may wish a stricter selector to check rql's shape
        __select__ = is_instance('Resource')
        # my table headers
        headers  = ['Resource', 'diem', 'duration', 'workpackage', 'description', 'state']
        # I want a table where attributes are editable (reledit inside)
        finalvid = 'editable-final'

        cellvids = {3: 'editable-final'}
        # display facets and actions with a menu
        layout_args = {'display_filter': 'top',
                       'add_view_actions': None}

To obtain an editable table, you may specify the 'editable-table' view identifier
using some of `cellvids`, `finalvid` or `nonfinalvid`.

The previous example results in:

.. image:: ../../../images/views-table-shadow.png

In order to activate table filter mechanism, the `display_filter` option is given
as a layout argument. A small arrow will be displayed at the table's top right
corner. Clicking on `show filter form` action, will display the filter form as
below:

.. image:: ../../../images/views-table-filter-shadow.png

By the same way, you can display additional actions for the selected entities
by setting `add_view_actions` layout option to `True`. This will add actions
returned by the view's :meth:`~cubicweb.web.views.tableview.TableMixIn.table_actions`.

You can notice that all columns of the result set are not displayed. This is
because of given `headers`, implying to display only columns from 0 to
len(headers).

Also Notice that the `ResourcesTable` view relies on a particular rql shape
(which is not ensured by the way, the only checked thing is that the result set
contains instance of the `Resource` type). That usually implies that you can't
use this view for user specific queries (e.g. generated by facets or typed
manually).


So another option would be to write this view using
:class:`~cubicweb.web.views.tableview.EntityTableView`, as below.

.. sourcecode:: python

    class ResourcesTable(EntityTableView):
        __regid__ = 'resource.table'
        __select__ = is_instance('Resource')
        # table columns definition
        columns  = ['resource', 'diem', 'duration', 'workpackage', 'description', 'in_state']
        # I want a table where attributes are editable (reledit inside)
        finalvid = 'editable-final'
        # display facets and actions with a menu
        layout_args = {'display_filter': 'top',
                       'add_view_actions': None}

        def workpackage_cell(entity):
            activity = entity.reverse_done_in[0]
            activity.view('reledit', rtype='done_for', role='subject', w=w)
        def workpackage_sortvalue(entity):
            activity = entity.reverse_done_in[0]
            return activity.done_for[0].sortvalue()

        column_renderers = {
            'resource': MainEntityColRenderer(),
            'workpackage': EntityTableColRenderer(
               header='Workpackage',
               renderfunc=workpackage_cell,
               sortfunc=workpackage_sortvalue,),
            'in_state': EntityTableColRenderer(
               renderfunc=lambda w,x: w(x.cw_adapt_to('IWorkflowable').printable_state),
               sortfunc=lambda x: x.cw_adapt_to('IWorkflowable').printable_state),
         }

Notice the following point:

* `cell_<column>(w, entity)` will be searched for rendering the content of a
  cell. If not found, `column` is expected to be an attribute of `entity`.

* `cell_sortvalue_<column>(entity)` should return a typed value to use for
  javascript sorting or None for not sortable columns (the default).

* The :func:`etable_entity_sortvalue` decorator will set a 'sortvalue' function
  for the column containing the main entity (the one given as argument to all
  methods), which will call `entity.sortvalue()`.

* You can set a column header using the :func:`etable_header_title` decorator.
  This header will be translated. If it's not an already existing msgid, think
  to mark it using `_()` (the example supposes headers are schema defined msgid).


Pro/cons of each approach
`````````````````````````
:class:`EntityTableView` and :class:`RsetableView` provides basically the same
set of features, though they don't share the same properties. Let's try to sum
up pro and cons of each class.

* `EntityTableView` view is:

  - more verbose, but usually easier to understand

  - easily extended (easy to add/remove columns for instance)

  - doesn't rely on a particular rset shape. Simply give it a title and will be
    listed in the 'possible views' box if any.

* `RsetTableView` view is:

  - hard to beat to display barely a result set, or for cases where some of
    `headers`, `displaycols` or `cellvids` could be defined to enhance the table
    while you don't care about e.g. pagination or facets.

  - hardly extensible, as you usually have to change places where the view is
    called to modify the RQL (hence the view's result set shape).