diff -r 058bb3dc685f -r 0b59724cb3f2 cubicweb/web/views/facets.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/cubicweb/web/views/facets.py Sat Jan 16 13:48:51 2016 +0100
@@ -0,0 +1,435 @@
+# 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 .
+"""the facets box and some basic facets"""
+
+__docformat__ = "restructuredtext en"
+from cubicweb import _
+
+from warnings import warn
+
+from logilab.mtconverter import xml_escape
+from logilab.common.decorators import cachedproperty
+from logilab.common.registry import objectify_predicate, yes
+
+from cubicweb import tags
+from cubicweb.predicates import (non_final_entity, multi_lines_rset,
+ match_context_prop, relation_possible)
+from cubicweb.utils import json_dumps
+from cubicweb.uilib import css_em_num_value
+from cubicweb.view import AnyRsetView
+from cubicweb.web import component, facet as facetbase
+from cubicweb.web.views.ajaxcontroller import ajaxfunc
+
+def facets(req, rset, context, mainvar=None, **kwargs):
+ """return the base rql and a list of widgets for facets applying to the
+ given rset/context (cached version of :func:`_facet`)
+
+ :param req: A :class:`~cubicweb.req.RequestSessionBase` object
+ :param rset: A :class:`~cubicweb.rset.ResultSet`
+ :param context: A string that match the ``__regid__`` of a ``FacetFilter``
+ :param mainvar: A string that match a select var from the rset
+ """
+ try:
+ cache = req.__rset_facets
+ except AttributeError:
+ cache = req.__rset_facets = {}
+ try:
+ return cache[(rset, context, mainvar)]
+ except KeyError:
+ facets = _facets(req, rset, context, mainvar, **kwargs)
+ cache[(rset, context, mainvar)] = facets
+ return facets
+
+def _facets(req, rset, context, mainvar, **kwargs):
+ """return the base rql and a list of widgets for facets applying to the
+ given rset/context
+
+ :param req: A :class:`~cubicweb.req.RequestSessionBase` object
+ :param rset: A :class:`~cubicweb.rset.ResultSet`
+ :param context: A string that match the ``__regid__`` of a ``FacetFilter``
+ :param mainvar: A string that match a select var from the rset
+ """
+ ### initialisation
+ # XXX done by selectors, though maybe necessary when rset has been hijacked
+ # (e.g. contextview_selector matched)
+ origqlst = rset.syntax_tree()
+ # union not yet supported
+ if len(origqlst.children) != 1:
+ req.debug('facette disabled on union request %s', origqlst)
+ return None, ()
+ rqlst = origqlst.copy()
+ select = rqlst.children[0]
+ filtered_variable, baserql = facetbase.init_facets(rset, select, mainvar)
+ ### Selection
+ possible_facets = req.vreg['facets'].poss_visible_objects(
+ req, rset=rset, rqlst=origqlst, select=select,
+ context=context, filtered_variable=filtered_variable, **kwargs)
+ wdgs = [(facet, facet.get_widget()) for facet in possible_facets]
+ return baserql, [wdg for facet, wdg in wdgs if wdg is not None]
+
+
+@objectify_predicate
+def contextview_selector(cls, req, rset=None, row=None, col=None, view=None,
+ **kwargs):
+ if view:
+ try:
+ getcontext = getattr(view, 'filter_box_context_info')
+ except AttributeError:
+ return 0
+ rset = getcontext()[0]
+ if rset is None or rset.rowcount < 2:
+ return 0
+ wdgs = facets(req, rset, cls.__regid__, view=view)[1]
+ return len(wdgs)
+ return 0
+
+@objectify_predicate
+def has_facets(cls, req, rset=None, **kwargs):
+ if rset is None or rset.rowcount < 2:
+ return 0
+ wdgs = facets(req, rset, cls.__regid__, **kwargs)[1]
+ return len(wdgs)
+
+
+def filter_hiddens(w, baserql, wdgs, **kwargs):
+ kwargs['facets'] = ','.join(wdg.facet.__regid__ for wdg in wdgs)
+ kwargs['baserql'] = baserql
+ for key, val in kwargs.items():
+ w(u'' % (
+ key, xml_escape(val)))
+
+
+class FacetFilterMixIn(object):
+ """Mixin Class to generate Facet Filter Form
+
+ To generate the form, you need to explicitly call the following method:
+
+ .. automethod:: generate_form
+
+ The most useful function to override is:
+
+ .. automethod:: layout_widgets
+ """
+
+ needs_js = ['cubicweb.ajax.js', 'cubicweb.facets.js']
+ needs_css = ['cubicweb.facets.css']
+
+ def generate_form(self, w, rset, divid, vid, vidargs=None, mainvar=None,
+ paginate=False, cssclass='', hiddens=None, **kwargs):
+ """display a form to filter some view's content
+
+ :param w: Write function
+
+ :param rset: ResultSet to be filtered
+
+ :param divid: Dom ID of the div where the rendering of the view is done.
+ :type divid: string
+
+ :param vid: ID of the view display in the div
+ :type vid: string
+
+ :param paginate: Is the view paginated?
+ :type paginate: boolean
+
+ :param cssclass: Additional css classes to put on the form.
+ :type cssclass: string
+
+ :param hiddens: other hidden parametters to include in the forms.
+ :type hiddens: dict from extra keyword argument
+ """
+ # XXX Facet.context property hijacks an otherwise well-behaved
+ # vocabulary with its own notions
+ # Hence we whack here to avoid a clash
+ kwargs.pop('context', None)
+ baserql, wdgs = facets(self._cw, rset, context=self.__regid__,
+ mainvar=mainvar, **kwargs)
+ assert wdgs
+ self._cw.add_js(self.needs_js)
+ self._cw.add_css(self.needs_css)
+ self._cw.html_headers.define_var('facetLoadingMsg',
+ self._cw._('facet-loading-msg'))
+ if vidargs is not None:
+ warn("[3.14] vidargs is deprecated. Maybe you're using some TableView?",
+ DeprecationWarning, stacklevel=2)
+ else:
+ vidargs = {}
+ vidargs = dict((k, v) for k, v in vidargs.items() if v)
+ facetargs = xml_escape(json_dumps([divid, vid, paginate, vidargs]))
+ w(u'
\n')
+
+ def sorted_widgets(self, wdgs):
+ """sort widgets: by default sort by widget height, then according to
+ widget.order (the original widgets order)
+ """
+ return sorted(wdgs, key=lambda x: 99 * (not x.facet.start_unfolded) or x.height )
+
+ def layout_widgets(self, w, wdgs):
+ """layout widgets: by default simply render each of them
+ (i.e. succession of
)
+ """
+ for wdg in wdgs:
+ wdg.render(w=w)
+
+
+class FilterBox(FacetFilterMixIn, component.CtxComponent):
+ """filter results of a query"""
+ __regid__ = 'facet.filterbox'
+ __select__ = ((non_final_entity() & has_facets())
+ | contextview_selector()) # can't use has_facets because of
+ # contextview mecanism
+ context = 'left' # XXX doesn't support 'incontext', only 'left' or 'right'
+ title = _('facet.filters')
+ visible = True # functionality provided by the search box by default
+ order = 1
+
+ bk_linkbox_template = u'