[facets] refactor / cleanup facet api: more consistent variable naming and easier to reuse function. Closes #1796804
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Fri, 01 Jul 2011 15:26:43 +0200
changeset 7605 48abeac162fd
parent 7604 1eb6090311ff
child 7606 01b49ec8fe80
[facets] refactor / cleanup facet api: more consistent variable naming and easier to reuse function. Closes #1796804
web/facet.py
web/test/unittest_facet.py
web/test/unittest_views_searchrestriction.py
web/views/basecontrollers.py
web/views/facets.py
--- a/web/facet.py	Fri Jul 01 15:26:33 2011 +0200
+++ b/web/facet.py	Fri Jul 01 15:26:43 2011 +0200
@@ -74,13 +74,9 @@
                             context=iter(ptypes).next())
     return display_name(facet._cw, facet.rtype, form=facet.role)
 
-def filtered_variable(rqlst):
-    vref = rqlst.selection[0].iget_nodes(nodes.VariableRef).next()
-    return vref.variable
-
-def get_facet(req, facetid, rqlst, mainvar):
-    return req.vreg['facets'].object_by_id(facetid, req, rqlst=rqlst,
-                                           filtered_variable=mainvar)
+def get_facet(req, facetid, select, filtered_variable):
+    return req.vreg['facets'].object_by_id(facetid, req, select=select,
+                                           filtered_variable=filtered_variable)
 
 @deprecated('[3.13] filter_hiddens moved to cubicweb.web.views.facets with '
             'slightly modified prototype')
@@ -91,21 +87,35 @@
 
 ## rqlst manipulation functions used by facets ################################
 
-def prepare_facets_rqlst(rqlst, args=None):
+def init_facets(rset, select, mainvar=None):
+    rset.req.vreg.rqlhelper.annotate(select)
+    filtered_variable = get_filtered_variable(select, mainvar)
+    baserql = select.as_string(kwargs=rset.args) # before call to prepare_select
+    prepare_select(select, filtered_variable)
+    return filtered_variable, baserql
+
+def get_filtered_variable(select, mainvar=None):
+    """drop any limit/offset from select (in-place modification) and return the
+    variable whose name is `mainvar` or the first variable selected in column 0
+    """
+    select.set_limit(None)
+    select.set_offset(None)
+    if mainvar is None:
+        vref = select.selection[0].iget_nodes(nodes.VariableRef).next()
+        return vref.variable
+    return select.defined_vars[mainvar]
+
+def prepare_select(select, filtered_variable):
     """prepare a syntax tree to generate facet filters
 
     * remove ORDERBY/GROUPBY clauses
     * cleanup selection (remove everything)
     * undefine unnecessary variables
     * set DISTINCT
-    * unset LIMIT/OFFSET
+
+    Notice unset of LIMIT/OFFSET us expected to be done by a previous call to
+    :func:`get_filtered_variable`.
     """
-    assert len(rqlst.children) == 1, 'FIXME: union not yet supported'
-    select = rqlst.children[0]
-    mainvar = filtered_variable(select)
-    select.set_limit(None)
-    select.set_offset(None)
-    baserql = select.as_string(kwargs=args)
     # cleanup sort terms / group by
     select.remove_sort_terms()
     select.remove_groups()
@@ -115,14 +125,21 @@
         select.remove_selected(term)
     # remove unbound variables which only have some type restriction
     for dvar in select.defined_vars.values():
-        if not (dvar is mainvar or dvar.stinfo['relations']):
+        if not (dvar is filtered_variable or dvar.stinfo['relations']):
             select.undefine_variable(dvar)
     # global tree config: DISTINCT, LIMIT, OFFSET
     select.set_distinct(True)
-    return mainvar, baserql
 
+@deprecated('[3.13] use init_facets instead')
+def prepare_facets_rqlst(rqlst, args=None):
+    assert len(rqlst.children) == 1, 'FIXME: union not yet supported'
+    select = rqlst.children[0]
+    filtered_variable = get_filtered_variable(select)
+    baserql = select.as_string(args)
+    prepare_select(select, filtered_variable)
+    return filtered_variable, baserql
 
-def prepare_vocabulary_rqlst(rqlst, mainvar, rtype, role,
+def prepare_vocabulary_select(select, filtered_variable, rtype, role,
                               select_target_entity=True):
     """prepare a syntax tree to generate a filter vocabulary rql using the given
     relation:
@@ -131,23 +148,23 @@
     * add the new variable to GROUPBY clause if necessary
     * add the new variable to the selection
     """
-    newvar = _add_rtype_relation(rqlst, mainvar, rtype, role)[0]
+    newvar = _add_rtype_relation(select, filtered_variable, rtype, role)[0]
     if select_target_entity:
-        if rqlst.groupby:
-            rqlst.add_group_var(newvar)
-        rqlst.add_selected(newvar)
+        if select.groupby:
+            select.add_group_var(newvar)
+        select.add_selected(newvar)
     # add is restriction if necessary
-    if mainvar.stinfo['typerel'] is None:
-        etypes = frozenset(sol[mainvar.name] for sol in rqlst.solutions)
-        rqlst.add_type_restriction(mainvar, etypes)
+    if filtered_variable.stinfo['typerel'] is None:
+        etypes = frozenset(sol[filtered_variable.name] for sol in select.solutions)
+        select.add_type_restriction(filtered_variable, etypes)
     return newvar
 
 
-def insert_attr_select_relation(rqlst, mainvar, rtype, role, attrname,
+def insert_attr_select_relation(select, filtered_variable, rtype, role, attrname,
                                 sortfuncname=None, sortasc=True,
                                 select_target_entity=True):
     """modify a syntax tree to :
-    * link a new variable to `mainvar` through `rtype` (where mainvar has `role`)
+    * link a new variable to `filtered_variable` through `rtype` (where filtered_variable has `role`)
     * retrieve only the newly inserted variable and its `attrname`
 
     Sorting:
@@ -155,35 +172,35 @@
     * on `sortfuncname`(`attrname`) if `sortfuncname` is specified
     * no sort if `sortasc` is None
     """
-    cleanup_rqlst(rqlst, mainvar)
-    var = prepare_vocabulary_rqlst(rqlst, mainvar, rtype, role,
+    cleanup_select(select, filtered_variable)
+    var = prepare_vocabulary_select(select, filtered_variable, rtype, role,
                                    select_target_entity)
-    attrvar = rqlst.make_variable()
-    rqlst.add_relation(var, attrname, attrvar)
+    attrvar = select.make_variable()
+    select.add_relation(var, attrname, attrvar)
     # if query is grouped, we have to add the attribute variable
-    if rqlst.groupby:
-        if not attrvar in rqlst.groupby:
-            rqlst.add_group_var(attrvar)
+    #if select.groupby: XXX may not occur anymore
+    #    if not attrvar in select.groupby:
+    #        select.add_group_var(attrvar)
     if sortasc is not None:
-        _set_orderby(rqlst, attrvar, sortasc, sortfuncname)
+        _set_orderby(select, attrvar, sortasc, sortfuncname)
     # add attribute variable to selection
-    rqlst.add_selected(attrvar)
+    select.add_selected(attrvar)
     return var
 
 
-def cleanup_rqlst(rqlst, mainvar):
+def cleanup_select(select, filtered_variable):
     """cleanup tree from unnecessary restrictions:
     * attribute selection
     * optional relations linked to the main variable
     * mandatory relations linked to the main variable
     """
-    if rqlst.where is None:
+    if select.where is None:
         return
-    schema = rqlst.root.schema
+    schema = select.root.schema
     toremove = set()
-    vargraph = deepcopy(rqlst.vargraph) # graph representing links between variable
-    for rel in rqlst.where.get_nodes(nodes.Relation):
-        ovar = _may_be_removed(rel, schema, mainvar)
+    vargraph = deepcopy(select.vargraph) # graph representing links between variable
+    for rel in select.where.get_nodes(nodes.Relation):
+        ovar = _may_be_removed(rel, schema, filtered_variable)
         if ovar is not None:
             toremove.add(ovar)
     removed = set()
@@ -199,29 +216,29 @@
             if rel in removed:
                 # already removed
                 continue
-            rqlst.remove_node(rel)
+            select.remove_node(rel)
             removed.add(rel)
         rel = trvar.stinfo['typerel']
         if rel is not None and not rel in removed:
-            rqlst.remove_node(rel)
+            select.remove_node(rel)
             removed.add(rel)
         # cleanup groupby clause
-        if rqlst.groupby:
-            for vref in rqlst.groupby[:]:
+        if select.groupby:
+            for vref in select.groupby[:]:
                 if vref.name == trvarname:
-                    rqlst.remove_group_var(vref)
+                    select.remove_group_var(vref)
         # we can also remove all variables which are linked to this variable
         # and have no path to the main variable
         for ovarname in linkedvars:
-            if ovarname == mainvar.name:
+            if ovarname == filtered_variable.name:
                 continue
-            if not has_path(vargraph, ovarname, mainvar.name):
-                toremove.add(rqlst.defined_vars[ovarname])
+            if not has_path(vargraph, ovarname, filtered_variable.name):
+                toremove.add(select.defined_vars[ovarname])
 
 
-def _may_be_removed(rel, schema, mainvar):
+def _may_be_removed(rel, schema, variable):
     """if the given relation may be removed from the tree, return the variable
-    on the other side of `mainvar`, else return None
+    on the other side of `variable`, else return None
     Conditions:
     * the relation is an attribute selection of the main variable
     * the relation is optional relation linked to the main variable
@@ -230,7 +247,7 @@
     """
     lhs, rhs = rel.get_variable_parts()
     rschema = schema.rschema(rel.r_type)
-    if lhs.variable is mainvar:
+    if lhs.variable is variable:
         try:
             ovar = rhs.variable
         except AttributeError:
@@ -244,7 +261,7 @@
             return None
         opt = 'right'
         cardidx = 0
-    elif getattr(rhs, 'variable', None) is mainvar:
+    elif getattr(rhs, 'variable', None) is variable:
         ovar = lhs.variable
         opt = 'left'
         cardidx = 1
@@ -265,32 +282,32 @@
         return ovar
     return None
 
-def _make_relation(rqlst, mainvar, rtype, role):
-    newvar = rqlst.make_variable()
+def _make_relation(select, variable, rtype, role):
+    newvar = select.make_variable()
     if role == 'object':
-        rel = nodes.make_relation(newvar, rtype, (mainvar,), nodes.VariableRef)
+        rel = nodes.make_relation(newvar, rtype, (variable,), nodes.VariableRef)
     else:
-        rel = nodes.make_relation(mainvar, rtype, (newvar,), nodes.VariableRef)
+        rel = nodes.make_relation(variable, rtype, (newvar,), nodes.VariableRef)
     return newvar, rel
 
-def _add_rtype_relation(rqlst, mainvar, rtype, role):
-    """add a relation relying `mainvar` to entities linked by the `rtype`
-    relation (where `mainvar` has `role`)
+def _add_rtype_relation(select, variable, rtype, role):
+    """add a relation relying `variable` to entities linked by the `rtype`
+    relation (where `variable` has `role`)
 
     return the inserted variable for linked entities.
     """
-    newvar, newrel = _make_relation(rqlst, mainvar, rtype, role)
-    rqlst.add_restriction(newrel)
+    newvar, newrel = _make_relation(select, variable, rtype, role)
+    select.add_restriction(newrel)
     return newvar, newrel
 
 def _add_eid_restr(rel, restrvar, value):
     rrel = nodes.make_constant_restriction(restrvar, 'eid', value, 'Int')
     rel.parent.replace(rel, nodes.And(rel, rrel))
 
-def _remove_relation(rqlst, rel, var):
+def _remove_relation(select, rel, var):
     """remove a constraint relation from the syntax tree"""
     # remove the relation
-    rqlst.remove_node(rel)
+    select.remove_node(rel)
     # remove relations where the filtered variable appears on the
     # lhs and rhs is a constant restriction
     extra = []
@@ -300,24 +317,24 @@
         if vrel.children[0].variable is var:
             if not vrel.children[1].get_nodes(nodes.Constant):
                 extra.append(vrel)
-            rqlst.remove_node(vrel)
+            select.remove_node(vrel)
     return extra
 
-def _set_orderby(rqlst, newvar, sortasc, sortfuncname):
+def _set_orderby(select, newvar, sortasc, sortfuncname):
     if sortfuncname is None:
-        rqlst.add_sort_var(newvar, sortasc)
+        select.add_sort_var(newvar, sortasc)
     else:
         vref = nodes.variable_ref(newvar)
         vref.register_reference()
         sortfunc = nodes.Function(sortfuncname)
         sortfunc.append(vref)
         term = nodes.SortTerm(sortfunc, sortasc)
-        rqlst.add_sort_term(term)
+        select.add_sort_term(term)
 
 
-_prepare_vocabulary_rqlst = deprecated('[3.13] renamed prepare_vocabulary_rqlst ')(
-    prepare_vocabulary_rqlst)
-_cleanup_rqlst = deprecated('[3.13] renamed to cleanup_rqlst')(cleanup_rqlst)
+_prepare_vocabulary_rqlst = deprecated('[3.13] renamed prepare_vocabulary_select')(
+    prepare_vocabulary_select)
+_cleanup_rqlst = deprecated('[3.13] renamed to cleanup_select')(cleanup_select)
 
 
 ## base facet classes ##########################################################
@@ -343,7 +360,8 @@
     Facets will have the following attributes set (beside the standard
     :class:`~cubicweb.appobject.AppObject` ones):
 
-    * `rqlst`, the rql syntax tree being facetted
+    * `select`, the :class:`rql.stmts.Select` node of the rql syntax tree being
+      filtered
 
     * `filtered_variable`, the variable node in this rql syntax tree that we're
       interested in filtering
@@ -373,16 +391,19 @@
     start_unfolded = True
     cw_rset = None # ensure facets have a cw_rset attribute
 
-    def __init__(self, req, rqlst=None, filtered_variable=None,
+    def __init__(self, req, select=None, filtered_variable=None,
                  **kwargs):
         super(AbstractFacet, self).__init__(req, **kwargs)
-        assert rqlst is not None
+        assert select is not None
         assert filtered_variable
         # take care: facet may be retreived using `object_by_id` from an ajax call
         # or from `select` using the result set to filter
-        self.rqlst = rqlst
+        self.select = select
         self.filtered_variable = filtered_variable
 
+    def __repr__(self):
+        return '<%s>' % self.__class__.__name__
+
     @property
     def operator(self):
         """Return the operator (AND or OR) to use for this facet when multiple
@@ -411,7 +432,7 @@
         """When some facet criteria has been updated, this method is called to
         add restriction for this facet into the rql syntax tree. It should get
         back its value in form parameters, and modify the syntax tree
-        (`self.rqlst`) accordingly.
+        (`self.select`) accordingly.
         """
         raise NotImplementedError
 
@@ -419,6 +440,11 @@
     def wdgclass(self):
         raise NotImplementedError
 
+    @property
+    @deprecated('[3.13] renamed .select')
+    def rqlst(self):
+        return self.select
+
 
 class VocabularyFacet(AbstractFacet):
     """This abstract class extend :class:`AbstractFacet` to use the
@@ -552,6 +578,79 @@
     title = property(rtype_facet_title)
     no_relation_label = '<no relation>'
 
+    def __repr__(self):
+        return '<%s on (%s-%s)>' % (self.__class__.__name__, self.rtype, self.role)
+
+    # facet public API #########################################################
+
+    def vocabulary(self):
+        """return vocabulary for this facet, eg a list of 2-uple (label, value)
+        """
+        select = self.select
+        select.save_state()
+        if self.rql_sort:
+            sort = self.sortasc
+        else:
+            sort = None # will be sorted on label
+        try:
+            var = insert_attr_select_relation(
+                select, self.filtered_variable, self.rtype, self.role,
+                self.target_attr, self.sortfunc, sort,
+                self._select_target_entity)
+            if self.target_type is not None:
+                select.add_type_restriction(var, self.target_type)
+            try:
+                rset = self.rqlexec(select.as_string(), self.cw_rset.args)
+            except:
+                self.exception('error while getting vocabulary for %s, rql: %s',
+                               self, select.as_string())
+                return ()
+        finally:
+            select.recover()
+        # don't call rset_vocabulary on empty result set, it may be an empty
+        # *list* (see rqlexec implementation)
+        values = rset and self.rset_vocabulary(rset) or []
+        if self._include_no_relation():
+            values.insert(0, (self._cw._(self.no_relation_label), ''))
+        return values
+
+    def possible_values(self):
+        """return a list of possible values (as string since it's used to
+        compare to a form value in javascript) for this facet
+        """
+        select = self.select
+        select.save_state()
+        try:
+            cleanup_select(select, self.filtered_variable)
+            if self._select_target_entity:
+                prepare_vocabulary_select(select, self.filtered_variable, self.rtype,
+                                         self.role, select_target_entity=True)
+            else:
+                insert_attr_select_relation(
+                    select, self.filtered_variable, self.rtype, self.role, self.target_attr,
+                    select_target_entity=False)
+            values = [unicode(x) for x, in self.rqlexec(select.as_string())]
+        except:
+            self.exception('while computing values for %s', self)
+            return []
+        finally:
+            select.recover()
+        if self._include_no_relation():
+            values.append('')
+        return values
+
+    def add_rql_restrictions(self):
+        """add restriction for this facet into the rql syntax tree"""
+        value = self._cw.form.get(self.__regid__)
+        if value is None:
+            return
+        filtered_variable = self.filtered_variable
+        restrvar, rel = _add_rtype_relation(self.select, filtered_variable,
+                                            self.rtype, self.role)
+        self.value_restriction(restrvar, rel, value)
+
+    # internal control API #####################################################
+
     @property
     def i18nable(self):
         """should label be internationalized"""
@@ -577,62 +676,6 @@
         return self.sortfunc is not None or (self.label_vid is None
                                              and not self.i18nable)
 
-    def vocabulary(self):
-        """return vocabulary for this facet, eg a list of 2-uple (label, value)
-        """
-        rqlst = self.rqlst
-        rqlst.save_state()
-        if self.rql_sort:
-            sort = self.sortasc
-        else:
-            sort = None # will be sorted on label
-        try:
-            mainvar = self.filtered_variable
-            var = insert_attr_select_relation(
-                rqlst, mainvar, self.rtype, self.role, self.target_attr,
-                self.sortfunc, sort, self._select_target_entity)
-            if self.target_type is not None:
-                rqlst.add_type_restriction(var, self.target_type)
-            try:
-                rset = self.rqlexec(rqlst.as_string(), self.cw_rset.args)
-            except:
-                self.exception('error while getting vocabulary for %s, rql: %s',
-                               self, rqlst.as_string())
-                return ()
-        finally:
-            rqlst.recover()
-        # don't call rset_vocabulary on empty result set, it may be an empty
-        # *list* (see rqlexec implementation)
-        values = rset and self.rset_vocabulary(rset) or []
-        if self._include_no_relation():
-            values.insert(0, (self._cw._(self.no_relation_label), ''))
-        return values
-
-    def possible_values(self):
-        """return a list of possible values (as string since it's used to
-        compare to a form value in javascript) for this facet
-        """
-        rqlst = self.rqlst
-        rqlst.save_state()
-        try:
-            cleanup_rqlst(rqlst, self.filtered_variable)
-            if self._select_target_entity:
-                prepare_vocabulary_rqlst(rqlst, self.filtered_variable, self.rtype,
-                                         self.role, select_target_entity=True)
-            else:
-                insert_attr_select_relation(
-                    rqlst, self.filtered_variable, self.rtype, self.role, self.target_attr,
-                    select_target_entity=False)
-            values = [unicode(x) for x, in self.rqlexec(rqlst.as_string())]
-        except:
-            self.exception('while computing values for %s', self)
-            return []
-        finally:
-            rqlst.recover()
-        if self._include_no_relation():
-            values.append('')
-        return values
-
     def rset_vocabulary(self, rset):
         if self.i18nable:
             _ = self._cw._
@@ -654,24 +697,19 @@
     def support_and(self):
         return self._search_card('+*')
 
-    def add_rql_restrictions(self):
-        """add restriction for this facet into the rql syntax tree"""
-        value = self._cw.form.get(self.__regid__)
-        if value is None:
-            return
-        mainvar = self.filtered_variable
-        restrvar, rel = _add_rtype_relation(self.rqlst, mainvar, self.rtype,
-                                            self.role)
+    # internal utilities #######################################################
+
+    def value_restriction(self, restrvar, rel, value):
         if isinstance(value, basestring):
             # only one value selected
             if value:
-                self.rqlst.add_eid_restriction(restrvar, value)
+                self.select.add_eid_restriction(restrvar, value)
             else:
                 rel.parent.replace(rel, nodes.Not(rel))
         elif self.operator == 'OR':
             # set_distinct only if rtype cardinality is > 1
             if self.support_and():
-                self.rqlst.set_distinct(True)
+                self.select.set_distinct(True)
             # multiple ORed values: using IN is fine
             if '' in value:
                 value.remove('')
@@ -684,7 +722,7 @@
                 self._add_not_rel_restr(rel)
             _add_eid_restr(rel, restrvar, value.pop())
             while value:
-                restrvar, rtrel = _make_relation(self.rqlst, mainvar,
+                restrvar, rtrel = _make_relation(self.select, filtered_variable,
                                                  self.rtype, self.role)
                 _add_eid_restr(rel, restrvar, value.pop())
 
@@ -720,19 +758,19 @@
         if self._cw.vreg.schema.rschema(self.rtype).final:
             return False
         if self.role == 'object':
-            subj = utils.rqlvar_maker(defined=self.rqlst.defined_vars,
-                                      aliases=self.rqlst.aliases).next()
+            subj = utils.rqlvar_maker(defined=self.select.defined_vars,
+                                      aliases=self.select.aliases).next()
             obj = self.filtered_variable.name
         else:
             subj = self.filtered_variable.name
-            obj = utils.rqlvar_maker(defined=self.rqlst.defined_vars,
-                                     aliases=self.rqlst.aliases).next()
+            obj = utils.rqlvar_maker(defined=self.select.defined_vars,
+                                     aliases=self.select.aliases).next()
         restrictions = []
-        if self.rqlst.where:
-            restrictions.append(self.rqlst.where.as_string())
-        if self.rqlst.with_:
+        if self.select.where:
+            restrictions.append(self.select.where.as_string())
+        if self.select.with_:
             restrictions.append('WITH ' + ','.join(
-                term.as_string() for term in self.rqlst.with_))
+                term.as_string() for term in self.select.with_))
         if restrictions:
             restrictions = ',' + ','.join(restrictions)
         else:
@@ -748,7 +786,7 @@
             return False
 
     def _add_not_rel_restr(self, rel):
-        nrrel = nodes.Not(_make_relation(self.rqlst, self.filtered_variable,
+        nrrel = nodes.Not(_make_relation(self.select, self.filtered_variable,
                                          self.rtype, self.role)[1])
         rel.parent.replace(rel, nodes.Or(nrrel, rel))
 
@@ -814,24 +852,24 @@
         value = self._cw.form.get(self.__regid__)
         if not value:
             return
-        mainvar = self.filtered_variable
-        restrvar = _add_rtype_relation(self.rqlst, mainvar, self.rtype,
+        filtered_variable = self.filtered_variable
+        restrvar = _add_rtype_relation(self.select, filtered_variable, self.rtype,
                                        self.role)[0]
-        self.rqlst.set_distinct(True)
+        self.select.set_distinct(True)
         if isinstance(value, basestring) or self.operator == 'OR':
             # only one value selected or multiple ORed values: using IN is fine
-            self.rqlst.add_constant_restriction(
+            self.select.add_constant_restriction(
                 restrvar, self.target_attr, value,
                 self.attrtype, self.comparator)
         else:
             # multiple values with AND operator
-            self.rqlst.add_constant_restriction(
+            self.select.add_constant_restriction(
                 restrvar, self.target_attr, value.pop(),
                 self.attrtype, self.comparator)
             while value:
-                restrvar = _add_rtype_relation(self.rqlst, mainvar, self.rtype,
+                restrvar = _add_rtype_relation(self.select, filtered_variable, self.rtype,
                                                self.role)[0]
-                self.rqlst.add_constant_restriction(
+                self.select.add_constant_restriction(
                     restrvar, self.target_attr, value.pop(),
                     self.attrtype, self.comparator)
 
@@ -883,21 +921,21 @@
     def vocabulary(self):
         """return vocabulary for this facet, eg a list of 2-uple (label, value)
         """
-        rqlst = self.rqlst
-        rqlst.save_state()
+        select = self.select
+        select.save_state()
         try:
-            mainvar = self.filtered_variable
-            cleanup_rqlst(rqlst, mainvar)
-            newvar = prepare_vocabulary_rqlst(rqlst, mainvar, self.rtype, self.role)
-            _set_orderby(rqlst, newvar, self.sortasc, self.sortfunc)
+            filtered_variable = self.filtered_variable
+            cleanup_select(select, filtered_variable)
+            newvar = prepare_vocabulary_select(select, filtered_variable, self.rtype, self.role)
+            _set_orderby(select, newvar, self.sortasc, self.sortfunc)
             try:
-                rset = self.rqlexec(rqlst.as_string(), self.cw_rset.args)
+                rset = self.rqlexec(select.as_string(), self.cw_rset.args)
             except:
                 self.exception('error while getting vocabulary for %s, rql: %s',
-                               self, rqlst.as_string())
+                               self, select.as_string())
                 return ()
         finally:
-            rqlst.recover()
+            select.recover()
         # don't call rset_vocabulary on empty result set, it may be an empty
         # *list* (see rqlexec implementation)
         return rset and self.rset_vocabulary(rset)
@@ -910,8 +948,8 @@
         value = self._cw.form.get(self.__regid__)
         if not value:
             return
-        mainvar = self.filtered_variable
-        self.rqlst.add_constant_restriction(mainvar, self.rtype, value,
+        filtered_variable = self.filtered_variable
+        self.select.add_constant_restriction(filtered_variable, self.rtype, value,
                                             self.attrtype, self.comparator)
 
 
@@ -1050,15 +1088,15 @@
 
     def add_rql_restrictions(self):
         """add restriction for this facet into the rql syntax tree"""
-        self.rqlst.set_distinct(True) # XXX
+        self.select.set_distinct(True) # XXX
         value = self._cw.form.get(self.__regid__)
         if not value: # no value sent for this facet
             return
-        var = self.rqlst.make_variable()
+        var = self.select.make_variable()
         if self.role == 'subject':
-            self.rqlst.add_relation(self.filtered_variable, self.rtype, var)
+            self.select.add_relation(self.filtered_variable, self.rtype, var)
         else:
-            self.rqlst.add_relation(var, self.rtype, self.filtered_variable)
+            self.select.add_relation(var, self.rtype, self.filtered_variable)
 
 
 ## html widets ################################################################
@@ -1131,7 +1169,7 @@
         values: [%(minvalue)s, %(maxvalue)s],
         stop: function(event, ui) { // submit when the user stops sliding
            var form = $('#%(sliderid)s').closest('form');
-           buildRQL.apply(null, evalJSON(form.attr('cubicweb:facetargs')));
+           buildRQL.apply(null, cw.evalJSON(form.attr('cubicweb:facetargs')));
         },
         slide: function(event, ui) {
             jQuery('#%(sliderid)s_inf').html(_formatter(ui.values[0]));
@@ -1286,15 +1324,15 @@
     def __init__(self, req):
         self._cw = req
 
-    def build_rql(self):#, tablefilter=False):
+    def build_rql(self):
         form = self._cw.form
         facetids = form['facets'].split(',')
         # XXX Union unsupported yet
         select = self._cw.vreg.parse(self._cw, form['baserql']).children[0]
-        mainvar = filtered_variable(select)
+        filtered_variable = get_filtered_variable(select, form.get('mainvar'))
         toupdate = []
         for facetid in facetids:
-            facet = get_facet(self._cw, facetid, select, mainvar)
+            facet = get_facet(self._cw, facetid, select, filtered_variable)
             facet.add_rql_restrictions()
             if facet.needs_update:
                 toupdate.append(facetid)
--- a/web/test/unittest_facet.py	Fri Jul 01 15:26:33 2011 +0200
+++ b/web/test/unittest_facet.py	Fri Jul 01 15:26:43 2011 +0200
@@ -7,18 +7,17 @@
         req = self.request()
         rset = self.execute('CWUser X')
         rqlst = rset.syntax_tree().copy()
-        req.vreg.rqlhelper.annotate(rqlst)
-        mainvar, baserql = facet.prepare_facets_rqlst(rqlst, rset.args)
-        self.assertEqual(mainvar.name, 'X')
+        filtered_variable, baserql = facet.init_facets(rset, rqlst.children[0])
+        self.assertEqual(filtered_variable.name, 'X')
         self.assertEqual(baserql, 'Any X WHERE X is CWUser')
         self.assertEqual(rqlst.as_string(), 'DISTINCT Any  WHERE X is CWUser')
-        return req, rset, rqlst, mainvar
+        return req, rset, rqlst, filtered_variable
 
     def _in_group_facet(self, cls=facet.RelationFacet, no_relation=False):
-        req, rset, rqlst, mainvar = self.prepare_rqlst()
+        req, rset, rqlst, filtered_variable = self.prepare_rqlst()
         cls.no_relation = no_relation
-        f = cls(req, rset=rset, rqlst=rqlst.children[0],
-                filtered_variable=mainvar)
+        f = cls(req, rset=rset, select=rqlst.children[0],
+                filtered_variable=filtered_variable)
         f.__regid__ = 'in_group'
         f.rtype = 'in_group'
         f.role = 'subject'
@@ -34,17 +33,17 @@
         self.assertEqual(f.vocabulary(),
                       [(u'guests', guests), (u'managers', managers)])
         # ensure rqlst is left unmodified
-        self.assertEqual(f.rqlst.as_string(), 'DISTINCT Any  WHERE X is CWUser')
+        self.assertEqual(f.select.as_string(), 'DISTINCT Any  WHERE X is CWUser')
         #rqlst = rset.syntax_tree()
         self.assertEqual(f.possible_values(),
                           [str(guests), str(managers)])
         # ensure rqlst is left unmodified
-        self.assertEqual(f.rqlst.as_string(), 'DISTINCT Any  WHERE X is CWUser')
+        self.assertEqual(f.select.as_string(), 'DISTINCT Any  WHERE X is CWUser')
         f._cw.form[f.__regid__] = str(guests)
         f.add_rql_restrictions()
         # selection is cluttered because rqlst has been prepared for facet (it
         # is not in real life)
-        self.assertEqual(f.rqlst.as_string(),
+        self.assertEqual(f.select.as_string(),
                           'DISTINCT Any  WHERE X is CWUser, X in_group D, D eid %s' % guests)
 
     def test_relation_optional_rel(self):
@@ -52,12 +51,12 @@
         rset = self.execute('Any X,GROUP_CONCAT(GN) GROUPBY X '
                             'WHERE X in_group G?, G name GN, NOT G name "users"')
         rqlst = rset.syntax_tree().copy()
-        req.vreg.rqlhelper.annotate(rqlst)
-        mainvar, baserql = facet.prepare_facets_rqlst(rqlst, rset.args)
+        select = rqlst.children[0]
+        filtered_variable, baserql = facet.init_facets(rset, select)
 
         f = facet.RelationFacet(req, rset=rset,
-                                rqlst=rqlst.children[0],
-                                filtered_variable=mainvar)
+                                select=select,
+                                filtered_variable=filtered_variable)
         f.rtype = 'in_group'
         f.role = 'subject'
         f.target_attr = 'name'
@@ -92,18 +91,18 @@
                           [str(guests), str(managers), ''])
         f._cw.form[f.__regid__] = ''
         f.add_rql_restrictions()
-        self.assertEqual(f.rqlst.as_string(),
+        self.assertEqual(f.select.as_string(),
                           'DISTINCT Any  WHERE X is CWUser, NOT X in_group G')
 
     def test_relation_no_relation_2(self):
         f, (guests, managers) = self._in_group_facet(no_relation=True)
         f._cw.form[f.__regid__] = ['', guests]
-        f.rqlst.save_state()
+        f.select.save_state()
         f.add_rql_restrictions()
-        self.assertEqual(f.rqlst.as_string(),
+        self.assertEqual(f.select.as_string(),
                           'DISTINCT Any  WHERE X is CWUser, (NOT X in_group B) OR (X in_group A, A eid %s)' % guests)
-        f.rqlst.recover()
-        self.assertEqual(f.rqlst.as_string(),
+        f.select.recover()
+        self.assertEqual(f.select.as_string(),
                           'DISTINCT Any  WHERE X is CWUser')
 
 
@@ -113,25 +112,25 @@
         self.assertEqual(f.vocabulary(),
                           [(u'guests', u'guests'), (u'managers', u'managers')])
         # ensure rqlst is left unmodified
-        self.assertEqual(f.rqlst.as_string(), 'DISTINCT Any  WHERE X is CWUser')
+        self.assertEqual(f.select.as_string(), 'DISTINCT Any  WHERE X is CWUser')
         #rqlst = rset.syntax_tree()
         self.assertEqual(f.possible_values(),
                           ['guests', 'managers'])
         # ensure rqlst is left unmodified
-        self.assertEqual(f.rqlst.as_string(), 'DISTINCT Any  WHERE X is CWUser')
+        self.assertEqual(f.select.as_string(), 'DISTINCT Any  WHERE X is CWUser')
         f._cw.form[f.__regid__] = 'guests'
         f.add_rql_restrictions()
         # selection is cluttered because rqlst has been prepared for facet (it
         # is not in real life)
-        self.assertEqual(f.rqlst.as_string(),
+        self.assertEqual(f.select.as_string(),
                           "DISTINCT Any  WHERE X is CWUser, X in_group E, E name 'guests'")
 
 
     def test_attribute(self):
-        req, rset, rqlst, mainvar = self.prepare_rqlst()
+        req, rset, rqlst, filtered_variable = self.prepare_rqlst()
         f = facet.AttributeFacet(req, rset=rset,
-                                 rqlst=rqlst.children[0],
-                                 filtered_variable=mainvar)
+                                 select=rqlst.children[0],
+                                 filtered_variable=filtered_variable)
         f.rtype = 'login'
         self.assertEqual(f.vocabulary(),
                           [(u'admin', u'admin'), (u'anon', u'anon')])
@@ -146,7 +145,7 @@
         f.add_rql_restrictions()
         # selection is cluttered because rqlst has been prepared for facet (it
         # is not in real life)
-        self.assertEqual(f.rqlst.as_string(),
+        self.assertEqual(f.select.as_string(),
                           "DISTINCT Any  WHERE X is CWUser, X login 'admin'")
 
 
--- a/web/test/unittest_views_searchrestriction.py	Fri Jul 01 15:26:33 2011 +0200
+++ b/web/test/unittest_views_searchrestriction.py	Fri Jul 01 15:26:43 2011 +0200
@@ -1,4 +1,4 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -15,11 +15,9 @@
 #
 # You should have received a copy of the GNU Lesser General Public License along
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
-"""
 
-"""
 from cubicweb.devtools.testlib import CubicWebTC
-from cubicweb.web.facet import insert_attr_select_relation, prepare_facets_rqlst
+from cubicweb.web import facet
 
 
 class InsertAttrRelationTC(CubicWebTC):
@@ -27,13 +25,14 @@
     def parse(self, query):
         rqlst = self.vreg.parse(self.session, query)
         select = rqlst.children[0]
-        # XXX done in real life?
-        select.remove_groups()
         return rqlst
 
     def _generate(self, rqlst, rel, role, attr):
-        mainvar = prepare_facets_rqlst(rqlst)[0]
-        insert_attr_select_relation(rqlst.children[0], mainvar, rel, role, attr)
+        select = rqlst.children[0]
+        filtered_variable = facet.get_filtered_variable(select)
+        facet.prepare_select(select, filtered_variable)
+        facet.insert_attr_select_relation(select, filtered_variable,
+                                          rel, role, attr)
         return rqlst.as_string()
 
     @property
--- a/web/views/basecontrollers.py	Fri Jul 01 15:26:33 2011 +0200
+++ b/web/views/basecontrollers.py	Fri Jul 01 15:26:43 2011 +0200
@@ -35,11 +35,9 @@
 from cubicweb.web.views import vid_from_rset, formrenderers
 
 try:
-    from cubicweb.web.facet import (FilterRQLBuilder, get_facet,
-                                    prepare_facets_rqlst)
-    HAS_SEARCH_RESTRICTION = True
+    from cubicweb.web import facet as facetbase
 except ImportError: # gae
-    HAS_SEARCH_RESTRICTION = False
+    facetbase = None
 
 def jsonize(func):
     """decorator to sets correct content_type and calls `json_dumps` on
@@ -490,18 +488,20 @@
             return None
         return cb(self._cw)
 
-    if HAS_SEARCH_RESTRICTION:
+    if facetbase is not None:
         @jsonize
         def js_filter_build_rql(self, names, values):
             form = self._rebuild_posted_form(names, values)
             self._cw.form = form
-            builder = FilterRQLBuilder(self._cw)
+            builder = facetbase.FilterRQLBuilder(self._cw)
             return builder.build_rql()
 
         @jsonize
-        def js_filter_select_content(self, facetids, rql):
-            rqlst = self._cw.vreg.parse(self._cw, rql) # XXX Union unsupported yet
-            mainvar = prepare_facets_rqlst(rqlst)[0]
+        def js_filter_select_content(self, facetids, rql, mainvar):
+            # Union unsupported yet
+            select = self._cw.vreg.parse(self._cw, rql).children[0]
+            filtered_variable = facetbase.get_filtered_variable(select, mainvar)
+            facetbase.prepare_select(select, filtered_variable)
             update_map = {}
             for facetid in facetids:
                 facet = get_facet(self._cw, facetid, rqlst.children[0], mainvar)
--- a/web/views/facets.py	Fri Jul 01 15:26:33 2011 +0200
+++ b/web/views/facets.py	Fri Jul 01 15:26:43 2011 +0200
@@ -255,6 +255,7 @@
     rtype = 'in_state'
     target_attr = 'name'
 
+
 # inherit from RelationFacet to benefit from its possible_values implementation
 class ETypeFacet(facetbase.RelationFacet):
     __regid__ = 'etype-facet'
@@ -278,24 +279,25 @@
         value = self._cw.form.get(self.__regid__)
         if not value:
             return
-        self.rqlst.add_type_restriction(self.filtered_variable, value)
+        self.select.add_type_restriction(self.filtered_variable, value)
 
     def possible_values(self):
         """return a list of possible values (as string since it's used to
         compare to a form value in javascript) for this facet
         """
-        rqlst = self.rqlst
-        rqlst.save_state()
+        select = self.select
+        select.save_state()
         try:
-            facetbase.cleanup_rqlst(rqlst, self.filtered_variable)
-            etype_var = facetbase.prepare_vocabulary_rqlst(
-                rqlst, self.filtered_variable, self.rtype, self.role)
-            attrvar = rqlst.make_variable()
-            rqlst.add_selected(attrvar)
-            rqlst.add_relation(etype_var, 'name', attrvar)
-            return [etype for _, etype in self.rqlexec(rqlst.as_string())]
+            facetbase.cleanup_select(select, self.filtered_variable)
+            etype_var = facetbase.prepare_vocabulary_select(
+                select, self.filtered_variable, self.rtype, self.role)
+            attrvar = select.make_variable()
+            select.add_selected(attrvar)
+            select.add_relation(etype_var, 'name', attrvar)
+            return [etype for _, etype in self.rqlexec(select.as_string())]
         finally:
-            rqlst.recover()
+            select.recover()
+
 
 class HasTextFacet(facetbase.AbstractFacet):
     __select__ = relation_possible('has_text', 'subject') & match_context_prop()
@@ -325,4 +327,4 @@
         value = self._cw.form.get(self.__regid__)
         if not value:
             return
-        self.rqlst.add_constant_restriction(self.filtered_variable, 'has_text', value, 'String')
+        self.select.add_constant_restriction(self.filtered_variable, 'has_text', value, 'String')