web/facet.py
changeset 7605 48abeac162fd
parent 7604 1eb6090311ff
child 7607 d332ecfc224a
equal deleted inserted replaced
7604:1eb6090311ff 7605:48abeac162fd
    72     if len(ptypes) == 1:
    72     if len(ptypes) == 1:
    73         return display_name(facet._cw, facet.rtype, form=facet.role,
    73         return display_name(facet._cw, facet.rtype, form=facet.role,
    74                             context=iter(ptypes).next())
    74                             context=iter(ptypes).next())
    75     return display_name(facet._cw, facet.rtype, form=facet.role)
    75     return display_name(facet._cw, facet.rtype, form=facet.role)
    76 
    76 
    77 def filtered_variable(rqlst):
    77 def get_facet(req, facetid, select, filtered_variable):
    78     vref = rqlst.selection[0].iget_nodes(nodes.VariableRef).next()
    78     return req.vreg['facets'].object_by_id(facetid, req, select=select,
    79     return vref.variable
    79                                            filtered_variable=filtered_variable)
    80 
       
    81 def get_facet(req, facetid, rqlst, mainvar):
       
    82     return req.vreg['facets'].object_by_id(facetid, req, rqlst=rqlst,
       
    83                                            filtered_variable=mainvar)
       
    84 
    80 
    85 @deprecated('[3.13] filter_hiddens moved to cubicweb.web.views.facets with '
    81 @deprecated('[3.13] filter_hiddens moved to cubicweb.web.views.facets with '
    86             'slightly modified prototype')
    82             'slightly modified prototype')
    87 def filter_hiddens(w, **kwargs):
    83 def filter_hiddens(w, **kwargs):
    88     from cubicweb.web.views.facets import filter_hiddens
    84     from cubicweb.web.views.facets import filter_hiddens
    89     return filter_hiddens(w, wdgs=kwargs.pop('facets'))
    85     return filter_hiddens(w, wdgs=kwargs.pop('facets'))
    90 
    86 
    91 
    87 
    92 ## rqlst manipulation functions used by facets ################################
    88 ## rqlst manipulation functions used by facets ################################
    93 
    89 
    94 def prepare_facets_rqlst(rqlst, args=None):
    90 def init_facets(rset, select, mainvar=None):
       
    91     rset.req.vreg.rqlhelper.annotate(select)
       
    92     filtered_variable = get_filtered_variable(select, mainvar)
       
    93     baserql = select.as_string(kwargs=rset.args) # before call to prepare_select
       
    94     prepare_select(select, filtered_variable)
       
    95     return filtered_variable, baserql
       
    96 
       
    97 def get_filtered_variable(select, mainvar=None):
       
    98     """drop any limit/offset from select (in-place modification) and return the
       
    99     variable whose name is `mainvar` or the first variable selected in column 0
       
   100     """
       
   101     select.set_limit(None)
       
   102     select.set_offset(None)
       
   103     if mainvar is None:
       
   104         vref = select.selection[0].iget_nodes(nodes.VariableRef).next()
       
   105         return vref.variable
       
   106     return select.defined_vars[mainvar]
       
   107 
       
   108 def prepare_select(select, filtered_variable):
    95     """prepare a syntax tree to generate facet filters
   109     """prepare a syntax tree to generate facet filters
    96 
   110 
    97     * remove ORDERBY/GROUPBY clauses
   111     * remove ORDERBY/GROUPBY clauses
    98     * cleanup selection (remove everything)
   112     * cleanup selection (remove everything)
    99     * undefine unnecessary variables
   113     * undefine unnecessary variables
   100     * set DISTINCT
   114     * set DISTINCT
   101     * unset LIMIT/OFFSET
   115 
   102     """
   116     Notice unset of LIMIT/OFFSET us expected to be done by a previous call to
   103     assert len(rqlst.children) == 1, 'FIXME: union not yet supported'
   117     :func:`get_filtered_variable`.
   104     select = rqlst.children[0]
   118     """
   105     mainvar = filtered_variable(select)
       
   106     select.set_limit(None)
       
   107     select.set_offset(None)
       
   108     baserql = select.as_string(kwargs=args)
       
   109     # cleanup sort terms / group by
   119     # cleanup sort terms / group by
   110     select.remove_sort_terms()
   120     select.remove_sort_terms()
   111     select.remove_groups()
   121     select.remove_groups()
   112     # XXX remove aggregat from having
   122     # XXX remove aggregat from having
   113     # selection: only vocabulary entity
   123     # selection: only vocabulary entity
   114     for term in select.selection[:]:
   124     for term in select.selection[:]:
   115         select.remove_selected(term)
   125         select.remove_selected(term)
   116     # remove unbound variables which only have some type restriction
   126     # remove unbound variables which only have some type restriction
   117     for dvar in select.defined_vars.values():
   127     for dvar in select.defined_vars.values():
   118         if not (dvar is mainvar or dvar.stinfo['relations']):
   128         if not (dvar is filtered_variable or dvar.stinfo['relations']):
   119             select.undefine_variable(dvar)
   129             select.undefine_variable(dvar)
   120     # global tree config: DISTINCT, LIMIT, OFFSET
   130     # global tree config: DISTINCT, LIMIT, OFFSET
   121     select.set_distinct(True)
   131     select.set_distinct(True)
   122     return mainvar, baserql
   132 
   123 
   133 @deprecated('[3.13] use init_facets instead')
   124 
   134 def prepare_facets_rqlst(rqlst, args=None):
   125 def prepare_vocabulary_rqlst(rqlst, mainvar, rtype, role,
   135     assert len(rqlst.children) == 1, 'FIXME: union not yet supported'
       
   136     select = rqlst.children[0]
       
   137     filtered_variable = get_filtered_variable(select)
       
   138     baserql = select.as_string(args)
       
   139     prepare_select(select, filtered_variable)
       
   140     return filtered_variable, baserql
       
   141 
       
   142 def prepare_vocabulary_select(select, filtered_variable, rtype, role,
   126                               select_target_entity=True):
   143                               select_target_entity=True):
   127     """prepare a syntax tree to generate a filter vocabulary rql using the given
   144     """prepare a syntax tree to generate a filter vocabulary rql using the given
   128     relation:
   145     relation:
   129     * create a variable to filter on this relation
   146     * create a variable to filter on this relation
   130     * add the relation
   147     * add the relation
   131     * add the new variable to GROUPBY clause if necessary
   148     * add the new variable to GROUPBY clause if necessary
   132     * add the new variable to the selection
   149     * add the new variable to the selection
   133     """
   150     """
   134     newvar = _add_rtype_relation(rqlst, mainvar, rtype, role)[0]
   151     newvar = _add_rtype_relation(select, filtered_variable, rtype, role)[0]
   135     if select_target_entity:
   152     if select_target_entity:
   136         if rqlst.groupby:
   153         if select.groupby:
   137             rqlst.add_group_var(newvar)
   154             select.add_group_var(newvar)
   138         rqlst.add_selected(newvar)
   155         select.add_selected(newvar)
   139     # add is restriction if necessary
   156     # add is restriction if necessary
   140     if mainvar.stinfo['typerel'] is None:
   157     if filtered_variable.stinfo['typerel'] is None:
   141         etypes = frozenset(sol[mainvar.name] for sol in rqlst.solutions)
   158         etypes = frozenset(sol[filtered_variable.name] for sol in select.solutions)
   142         rqlst.add_type_restriction(mainvar, etypes)
   159         select.add_type_restriction(filtered_variable, etypes)
   143     return newvar
   160     return newvar
   144 
   161 
   145 
   162 
   146 def insert_attr_select_relation(rqlst, mainvar, rtype, role, attrname,
   163 def insert_attr_select_relation(select, filtered_variable, rtype, role, attrname,
   147                                 sortfuncname=None, sortasc=True,
   164                                 sortfuncname=None, sortasc=True,
   148                                 select_target_entity=True):
   165                                 select_target_entity=True):
   149     """modify a syntax tree to :
   166     """modify a syntax tree to :
   150     * link a new variable to `mainvar` through `rtype` (where mainvar has `role`)
   167     * link a new variable to `filtered_variable` through `rtype` (where filtered_variable has `role`)
   151     * retrieve only the newly inserted variable and its `attrname`
   168     * retrieve only the newly inserted variable and its `attrname`
   152 
   169 
   153     Sorting:
   170     Sorting:
   154     * on `attrname` ascendant (`sortasc`=True) or descendant (`sortasc`=False)
   171     * on `attrname` ascendant (`sortasc`=True) or descendant (`sortasc`=False)
   155     * on `sortfuncname`(`attrname`) if `sortfuncname` is specified
   172     * on `sortfuncname`(`attrname`) if `sortfuncname` is specified
   156     * no sort if `sortasc` is None
   173     * no sort if `sortasc` is None
   157     """
   174     """
   158     cleanup_rqlst(rqlst, mainvar)
   175     cleanup_select(select, filtered_variable)
   159     var = prepare_vocabulary_rqlst(rqlst, mainvar, rtype, role,
   176     var = prepare_vocabulary_select(select, filtered_variable, rtype, role,
   160                                    select_target_entity)
   177                                    select_target_entity)
   161     attrvar = rqlst.make_variable()
   178     attrvar = select.make_variable()
   162     rqlst.add_relation(var, attrname, attrvar)
   179     select.add_relation(var, attrname, attrvar)
   163     # if query is grouped, we have to add the attribute variable
   180     # if query is grouped, we have to add the attribute variable
   164     if rqlst.groupby:
   181     #if select.groupby: XXX may not occur anymore
   165         if not attrvar in rqlst.groupby:
   182     #    if not attrvar in select.groupby:
   166             rqlst.add_group_var(attrvar)
   183     #        select.add_group_var(attrvar)
   167     if sortasc is not None:
   184     if sortasc is not None:
   168         _set_orderby(rqlst, attrvar, sortasc, sortfuncname)
   185         _set_orderby(select, attrvar, sortasc, sortfuncname)
   169     # add attribute variable to selection
   186     # add attribute variable to selection
   170     rqlst.add_selected(attrvar)
   187     select.add_selected(attrvar)
   171     return var
   188     return var
   172 
   189 
   173 
   190 
   174 def cleanup_rqlst(rqlst, mainvar):
   191 def cleanup_select(select, filtered_variable):
   175     """cleanup tree from unnecessary restrictions:
   192     """cleanup tree from unnecessary restrictions:
   176     * attribute selection
   193     * attribute selection
   177     * optional relations linked to the main variable
   194     * optional relations linked to the main variable
   178     * mandatory relations linked to the main variable
   195     * mandatory relations linked to the main variable
   179     """
   196     """
   180     if rqlst.where is None:
   197     if select.where is None:
   181         return
   198         return
   182     schema = rqlst.root.schema
   199     schema = select.root.schema
   183     toremove = set()
   200     toremove = set()
   184     vargraph = deepcopy(rqlst.vargraph) # graph representing links between variable
   201     vargraph = deepcopy(select.vargraph) # graph representing links between variable
   185     for rel in rqlst.where.get_nodes(nodes.Relation):
   202     for rel in select.where.get_nodes(nodes.Relation):
   186         ovar = _may_be_removed(rel, schema, mainvar)
   203         ovar = _may_be_removed(rel, schema, filtered_variable)
   187         if ovar is not None:
   204         if ovar is not None:
   188             toremove.add(ovar)
   205             toremove.add(ovar)
   189     removed = set()
   206     removed = set()
   190     while toremove:
   207     while toremove:
   191         trvar = toremove.pop()
   208         trvar = toremove.pop()
   197         # remove relation using this variable
   214         # remove relation using this variable
   198         for rel in trvar.stinfo['relations']:
   215         for rel in trvar.stinfo['relations']:
   199             if rel in removed:
   216             if rel in removed:
   200                 # already removed
   217                 # already removed
   201                 continue
   218                 continue
   202             rqlst.remove_node(rel)
   219             select.remove_node(rel)
   203             removed.add(rel)
   220             removed.add(rel)
   204         rel = trvar.stinfo['typerel']
   221         rel = trvar.stinfo['typerel']
   205         if rel is not None and not rel in removed:
   222         if rel is not None and not rel in removed:
   206             rqlst.remove_node(rel)
   223             select.remove_node(rel)
   207             removed.add(rel)
   224             removed.add(rel)
   208         # cleanup groupby clause
   225         # cleanup groupby clause
   209         if rqlst.groupby:
   226         if select.groupby:
   210             for vref in rqlst.groupby[:]:
   227             for vref in select.groupby[:]:
   211                 if vref.name == trvarname:
   228                 if vref.name == trvarname:
   212                     rqlst.remove_group_var(vref)
   229                     select.remove_group_var(vref)
   213         # we can also remove all variables which are linked to this variable
   230         # we can also remove all variables which are linked to this variable
   214         # and have no path to the main variable
   231         # and have no path to the main variable
   215         for ovarname in linkedvars:
   232         for ovarname in linkedvars:
   216             if ovarname == mainvar.name:
   233             if ovarname == filtered_variable.name:
   217                 continue
   234                 continue
   218             if not has_path(vargraph, ovarname, mainvar.name):
   235             if not has_path(vargraph, ovarname, filtered_variable.name):
   219                 toremove.add(rqlst.defined_vars[ovarname])
   236                 toremove.add(select.defined_vars[ovarname])
   220 
   237 
   221 
   238 
   222 def _may_be_removed(rel, schema, mainvar):
   239 def _may_be_removed(rel, schema, variable):
   223     """if the given relation may be removed from the tree, return the variable
   240     """if the given relation may be removed from the tree, return the variable
   224     on the other side of `mainvar`, else return None
   241     on the other side of `variable`, else return None
   225     Conditions:
   242     Conditions:
   226     * the relation is an attribute selection of the main variable
   243     * the relation is an attribute selection of the main variable
   227     * the relation is optional relation linked to the main variable
   244     * the relation is optional relation linked to the main variable
   228     * the relation is a mandatory relation linked to the main variable
   245     * the relation is a mandatory relation linked to the main variable
   229       without any restriction on the other variable
   246       without any restriction on the other variable
   230     """
   247     """
   231     lhs, rhs = rel.get_variable_parts()
   248     lhs, rhs = rel.get_variable_parts()
   232     rschema = schema.rschema(rel.r_type)
   249     rschema = schema.rschema(rel.r_type)
   233     if lhs.variable is mainvar:
   250     if lhs.variable is variable:
   234         try:
   251         try:
   235             ovar = rhs.variable
   252             ovar = rhs.variable
   236         except AttributeError:
   253         except AttributeError:
   237             # constant restriction
   254             # constant restriction
   238             # XXX: X title LOWER(T) if it makes sense?
   255             # XXX: X title LOWER(T) if it makes sense?
   242                 # attribute selection
   259                 # attribute selection
   243                 return ovar
   260                 return ovar
   244             return None
   261             return None
   245         opt = 'right'
   262         opt = 'right'
   246         cardidx = 0
   263         cardidx = 0
   247     elif getattr(rhs, 'variable', None) is mainvar:
   264     elif getattr(rhs, 'variable', None) is variable:
   248         ovar = lhs.variable
   265         ovar = lhs.variable
   249         opt = 'left'
   266         opt = 'left'
   250         cardidx = 1
   267         cardidx = 1
   251     else:
   268     else:
   252         # not directly linked to the main variable
   269         # not directly linked to the main variable
   263             if _may_be_removed(orel, schema, ovar) is None:
   280             if _may_be_removed(orel, schema, ovar) is None:
   264                 return None
   281                 return None
   265         return ovar
   282         return ovar
   266     return None
   283     return None
   267 
   284 
   268 def _make_relation(rqlst, mainvar, rtype, role):
   285 def _make_relation(select, variable, rtype, role):
   269     newvar = rqlst.make_variable()
   286     newvar = select.make_variable()
   270     if role == 'object':
   287     if role == 'object':
   271         rel = nodes.make_relation(newvar, rtype, (mainvar,), nodes.VariableRef)
   288         rel = nodes.make_relation(newvar, rtype, (variable,), nodes.VariableRef)
   272     else:
   289     else:
   273         rel = nodes.make_relation(mainvar, rtype, (newvar,), nodes.VariableRef)
   290         rel = nodes.make_relation(variable, rtype, (newvar,), nodes.VariableRef)
   274     return newvar, rel
   291     return newvar, rel
   275 
   292 
   276 def _add_rtype_relation(rqlst, mainvar, rtype, role):
   293 def _add_rtype_relation(select, variable, rtype, role):
   277     """add a relation relying `mainvar` to entities linked by the `rtype`
   294     """add a relation relying `variable` to entities linked by the `rtype`
   278     relation (where `mainvar` has `role`)
   295     relation (where `variable` has `role`)
   279 
   296 
   280     return the inserted variable for linked entities.
   297     return the inserted variable for linked entities.
   281     """
   298     """
   282     newvar, newrel = _make_relation(rqlst, mainvar, rtype, role)
   299     newvar, newrel = _make_relation(select, variable, rtype, role)
   283     rqlst.add_restriction(newrel)
   300     select.add_restriction(newrel)
   284     return newvar, newrel
   301     return newvar, newrel
   285 
   302 
   286 def _add_eid_restr(rel, restrvar, value):
   303 def _add_eid_restr(rel, restrvar, value):
   287     rrel = nodes.make_constant_restriction(restrvar, 'eid', value, 'Int')
   304     rrel = nodes.make_constant_restriction(restrvar, 'eid', value, 'Int')
   288     rel.parent.replace(rel, nodes.And(rel, rrel))
   305     rel.parent.replace(rel, nodes.And(rel, rrel))
   289 
   306 
   290 def _remove_relation(rqlst, rel, var):
   307 def _remove_relation(select, rel, var):
   291     """remove a constraint relation from the syntax tree"""
   308     """remove a constraint relation from the syntax tree"""
   292     # remove the relation
   309     # remove the relation
   293     rqlst.remove_node(rel)
   310     select.remove_node(rel)
   294     # remove relations where the filtered variable appears on the
   311     # remove relations where the filtered variable appears on the
   295     # lhs and rhs is a constant restriction
   312     # lhs and rhs is a constant restriction
   296     extra = []
   313     extra = []
   297     for vrel in var.stinfo['relations']:
   314     for vrel in var.stinfo['relations']:
   298         if vrel is rel:
   315         if vrel is rel:
   299             continue
   316             continue
   300         if vrel.children[0].variable is var:
   317         if vrel.children[0].variable is var:
   301             if not vrel.children[1].get_nodes(nodes.Constant):
   318             if not vrel.children[1].get_nodes(nodes.Constant):
   302                 extra.append(vrel)
   319                 extra.append(vrel)
   303             rqlst.remove_node(vrel)
   320             select.remove_node(vrel)
   304     return extra
   321     return extra
   305 
   322 
   306 def _set_orderby(rqlst, newvar, sortasc, sortfuncname):
   323 def _set_orderby(select, newvar, sortasc, sortfuncname):
   307     if sortfuncname is None:
   324     if sortfuncname is None:
   308         rqlst.add_sort_var(newvar, sortasc)
   325         select.add_sort_var(newvar, sortasc)
   309     else:
   326     else:
   310         vref = nodes.variable_ref(newvar)
   327         vref = nodes.variable_ref(newvar)
   311         vref.register_reference()
   328         vref.register_reference()
   312         sortfunc = nodes.Function(sortfuncname)
   329         sortfunc = nodes.Function(sortfuncname)
   313         sortfunc.append(vref)
   330         sortfunc.append(vref)
   314         term = nodes.SortTerm(sortfunc, sortasc)
   331         term = nodes.SortTerm(sortfunc, sortasc)
   315         rqlst.add_sort_term(term)
   332         select.add_sort_term(term)
   316 
   333 
   317 
   334 
   318 _prepare_vocabulary_rqlst = deprecated('[3.13] renamed prepare_vocabulary_rqlst ')(
   335 _prepare_vocabulary_rqlst = deprecated('[3.13] renamed prepare_vocabulary_select')(
   319     prepare_vocabulary_rqlst)
   336     prepare_vocabulary_select)
   320 _cleanup_rqlst = deprecated('[3.13] renamed to cleanup_rqlst')(cleanup_rqlst)
   337 _cleanup_rqlst = deprecated('[3.13] renamed to cleanup_select')(cleanup_select)
   321 
   338 
   322 
   339 
   323 ## base facet classes ##########################################################
   340 ## base facet classes ##########################################################
   324 
   341 
   325 class AbstractFacet(AppObject):
   342 class AbstractFacet(AppObject):
   341     .. automethod:: cubicweb.web.facet.AbstractFacet.add_rql_restrictions
   358     .. automethod:: cubicweb.web.facet.AbstractFacet.add_rql_restrictions
   342 
   359 
   343     Facets will have the following attributes set (beside the standard
   360     Facets will have the following attributes set (beside the standard
   344     :class:`~cubicweb.appobject.AppObject` ones):
   361     :class:`~cubicweb.appobject.AppObject` ones):
   345 
   362 
   346     * `rqlst`, the rql syntax tree being facetted
   363     * `select`, the :class:`rql.stmts.Select` node of the rql syntax tree being
       
   364       filtered
   347 
   365 
   348     * `filtered_variable`, the variable node in this rql syntax tree that we're
   366     * `filtered_variable`, the variable node in this rql syntax tree that we're
   349       interested in filtering
   367       interested in filtering
   350 
   368 
   351     Facets implementors may also be interested in the following properties /
   369     Facets implementors may also be interested in the following properties /
   371     context = ''
   389     context = ''
   372     needs_update = False
   390     needs_update = False
   373     start_unfolded = True
   391     start_unfolded = True
   374     cw_rset = None # ensure facets have a cw_rset attribute
   392     cw_rset = None # ensure facets have a cw_rset attribute
   375 
   393 
   376     def __init__(self, req, rqlst=None, filtered_variable=None,
   394     def __init__(self, req, select=None, filtered_variable=None,
   377                  **kwargs):
   395                  **kwargs):
   378         super(AbstractFacet, self).__init__(req, **kwargs)
   396         super(AbstractFacet, self).__init__(req, **kwargs)
   379         assert rqlst is not None
   397         assert select is not None
   380         assert filtered_variable
   398         assert filtered_variable
   381         # take care: facet may be retreived using `object_by_id` from an ajax call
   399         # take care: facet may be retreived using `object_by_id` from an ajax call
   382         # or from `select` using the result set to filter
   400         # or from `select` using the result set to filter
   383         self.rqlst = rqlst
   401         self.select = select
   384         self.filtered_variable = filtered_variable
   402         self.filtered_variable = filtered_variable
       
   403 
       
   404     def __repr__(self):
       
   405         return '<%s>' % self.__class__.__name__
   385 
   406 
   386     @property
   407     @property
   387     def operator(self):
   408     def operator(self):
   388         """Return the operator (AND or OR) to use for this facet when multiple
   409         """Return the operator (AND or OR) to use for this facet when multiple
   389         values are selected.
   410         values are selected.
   409 
   430 
   410     def add_rql_restrictions(self):
   431     def add_rql_restrictions(self):
   411         """When some facet criteria has been updated, this method is called to
   432         """When some facet criteria has been updated, this method is called to
   412         add restriction for this facet into the rql syntax tree. It should get
   433         add restriction for this facet into the rql syntax tree. It should get
   413         back its value in form parameters, and modify the syntax tree
   434         back its value in form parameters, and modify the syntax tree
   414         (`self.rqlst`) accordingly.
   435         (`self.select`) accordingly.
   415         """
   436         """
   416         raise NotImplementedError
   437         raise NotImplementedError
   417 
   438 
   418     @property
   439     @property
   419     def wdgclass(self):
   440     def wdgclass(self):
   420         raise NotImplementedError
   441         raise NotImplementedError
       
   442 
       
   443     @property
       
   444     @deprecated('[3.13] renamed .select')
       
   445     def rqlst(self):
       
   446         return self.select
   421 
   447 
   422 
   448 
   423 class VocabularyFacet(AbstractFacet):
   449 class VocabularyFacet(AbstractFacet):
   424     """This abstract class extend :class:`AbstractFacet` to use the
   450     """This abstract class extend :class:`AbstractFacet` to use the
   425     :class:`FacetVocabularyWidget` as widget, suitable for facets that may
   451     :class:`FacetVocabularyWidget` as widget, suitable for facets that may
   550     _select_target_entity = True
   576     _select_target_entity = True
   551 
   577 
   552     title = property(rtype_facet_title)
   578     title = property(rtype_facet_title)
   553     no_relation_label = '<no relation>'
   579     no_relation_label = '<no relation>'
   554 
   580 
       
   581     def __repr__(self):
       
   582         return '<%s on (%s-%s)>' % (self.__class__.__name__, self.rtype, self.role)
       
   583 
       
   584     # facet public API #########################################################
       
   585 
       
   586     def vocabulary(self):
       
   587         """return vocabulary for this facet, eg a list of 2-uple (label, value)
       
   588         """
       
   589         select = self.select
       
   590         select.save_state()
       
   591         if self.rql_sort:
       
   592             sort = self.sortasc
       
   593         else:
       
   594             sort = None # will be sorted on label
       
   595         try:
       
   596             var = insert_attr_select_relation(
       
   597                 select, self.filtered_variable, self.rtype, self.role,
       
   598                 self.target_attr, self.sortfunc, sort,
       
   599                 self._select_target_entity)
       
   600             if self.target_type is not None:
       
   601                 select.add_type_restriction(var, self.target_type)
       
   602             try:
       
   603                 rset = self.rqlexec(select.as_string(), self.cw_rset.args)
       
   604             except:
       
   605                 self.exception('error while getting vocabulary for %s, rql: %s',
       
   606                                self, select.as_string())
       
   607                 return ()
       
   608         finally:
       
   609             select.recover()
       
   610         # don't call rset_vocabulary on empty result set, it may be an empty
       
   611         # *list* (see rqlexec implementation)
       
   612         values = rset and self.rset_vocabulary(rset) or []
       
   613         if self._include_no_relation():
       
   614             values.insert(0, (self._cw._(self.no_relation_label), ''))
       
   615         return values
       
   616 
       
   617     def possible_values(self):
       
   618         """return a list of possible values (as string since it's used to
       
   619         compare to a form value in javascript) for this facet
       
   620         """
       
   621         select = self.select
       
   622         select.save_state()
       
   623         try:
       
   624             cleanup_select(select, self.filtered_variable)
       
   625             if self._select_target_entity:
       
   626                 prepare_vocabulary_select(select, self.filtered_variable, self.rtype,
       
   627                                          self.role, select_target_entity=True)
       
   628             else:
       
   629                 insert_attr_select_relation(
       
   630                     select, self.filtered_variable, self.rtype, self.role, self.target_attr,
       
   631                     select_target_entity=False)
       
   632             values = [unicode(x) for x, in self.rqlexec(select.as_string())]
       
   633         except:
       
   634             self.exception('while computing values for %s', self)
       
   635             return []
       
   636         finally:
       
   637             select.recover()
       
   638         if self._include_no_relation():
       
   639             values.append('')
       
   640         return values
       
   641 
       
   642     def add_rql_restrictions(self):
       
   643         """add restriction for this facet into the rql syntax tree"""
       
   644         value = self._cw.form.get(self.__regid__)
       
   645         if value is None:
       
   646             return
       
   647         filtered_variable = self.filtered_variable
       
   648         restrvar, rel = _add_rtype_relation(self.select, filtered_variable,
       
   649                                             self.rtype, self.role)
       
   650         self.value_restriction(restrvar, rel, value)
       
   651 
       
   652     # internal control API #####################################################
       
   653 
   555     @property
   654     @property
   556     def i18nable(self):
   655     def i18nable(self):
   557         """should label be internationalized"""
   656         """should label be internationalized"""
   558         if self.target_type:
   657         if self.target_type:
   559             eschema = self._cw.vreg.schema.eschema(self.target_type)
   658             eschema = self._cw.vreg.schema.eschema(self.target_type)
   574         sortfunc is set or if we have not to transform the returned value (eg no
   673         sortfunc is set or if we have not to transform the returned value (eg no
   575         label_vid and not i18nable)
   674         label_vid and not i18nable)
   576         """
   675         """
   577         return self.sortfunc is not None or (self.label_vid is None
   676         return self.sortfunc is not None or (self.label_vid is None
   578                                              and not self.i18nable)
   677                                              and not self.i18nable)
   579 
       
   580     def vocabulary(self):
       
   581         """return vocabulary for this facet, eg a list of 2-uple (label, value)
       
   582         """
       
   583         rqlst = self.rqlst
       
   584         rqlst.save_state()
       
   585         if self.rql_sort:
       
   586             sort = self.sortasc
       
   587         else:
       
   588             sort = None # will be sorted on label
       
   589         try:
       
   590             mainvar = self.filtered_variable
       
   591             var = insert_attr_select_relation(
       
   592                 rqlst, mainvar, self.rtype, self.role, self.target_attr,
       
   593                 self.sortfunc, sort, self._select_target_entity)
       
   594             if self.target_type is not None:
       
   595                 rqlst.add_type_restriction(var, self.target_type)
       
   596             try:
       
   597                 rset = self.rqlexec(rqlst.as_string(), self.cw_rset.args)
       
   598             except:
       
   599                 self.exception('error while getting vocabulary for %s, rql: %s',
       
   600                                self, rqlst.as_string())
       
   601                 return ()
       
   602         finally:
       
   603             rqlst.recover()
       
   604         # don't call rset_vocabulary on empty result set, it may be an empty
       
   605         # *list* (see rqlexec implementation)
       
   606         values = rset and self.rset_vocabulary(rset) or []
       
   607         if self._include_no_relation():
       
   608             values.insert(0, (self._cw._(self.no_relation_label), ''))
       
   609         return values
       
   610 
       
   611     def possible_values(self):
       
   612         """return a list of possible values (as string since it's used to
       
   613         compare to a form value in javascript) for this facet
       
   614         """
       
   615         rqlst = self.rqlst
       
   616         rqlst.save_state()
       
   617         try:
       
   618             cleanup_rqlst(rqlst, self.filtered_variable)
       
   619             if self._select_target_entity:
       
   620                 prepare_vocabulary_rqlst(rqlst, self.filtered_variable, self.rtype,
       
   621                                          self.role, select_target_entity=True)
       
   622             else:
       
   623                 insert_attr_select_relation(
       
   624                     rqlst, self.filtered_variable, self.rtype, self.role, self.target_attr,
       
   625                     select_target_entity=False)
       
   626             values = [unicode(x) for x, in self.rqlexec(rqlst.as_string())]
       
   627         except:
       
   628             self.exception('while computing values for %s', self)
       
   629             return []
       
   630         finally:
       
   631             rqlst.recover()
       
   632         if self._include_no_relation():
       
   633             values.append('')
       
   634         return values
       
   635 
   678 
   636     def rset_vocabulary(self, rset):
   679     def rset_vocabulary(self, rset):
   637         if self.i18nable:
   680         if self.i18nable:
   638             _ = self._cw._
   681             _ = self._cw._
   639         else:
   682         else:
   652         return values
   695         return values
   653 
   696 
   654     def support_and(self):
   697     def support_and(self):
   655         return self._search_card('+*')
   698         return self._search_card('+*')
   656 
   699 
   657     def add_rql_restrictions(self):
   700     # internal utilities #######################################################
   658         """add restriction for this facet into the rql syntax tree"""
   701 
   659         value = self._cw.form.get(self.__regid__)
   702     def value_restriction(self, restrvar, rel, value):
   660         if value is None:
       
   661             return
       
   662         mainvar = self.filtered_variable
       
   663         restrvar, rel = _add_rtype_relation(self.rqlst, mainvar, self.rtype,
       
   664                                             self.role)
       
   665         if isinstance(value, basestring):
   703         if isinstance(value, basestring):
   666             # only one value selected
   704             # only one value selected
   667             if value:
   705             if value:
   668                 self.rqlst.add_eid_restriction(restrvar, value)
   706                 self.select.add_eid_restriction(restrvar, value)
   669             else:
   707             else:
   670                 rel.parent.replace(rel, nodes.Not(rel))
   708                 rel.parent.replace(rel, nodes.Not(rel))
   671         elif self.operator == 'OR':
   709         elif self.operator == 'OR':
   672             # set_distinct only if rtype cardinality is > 1
   710             # set_distinct only if rtype cardinality is > 1
   673             if self.support_and():
   711             if self.support_and():
   674                 self.rqlst.set_distinct(True)
   712                 self.select.set_distinct(True)
   675             # multiple ORed values: using IN is fine
   713             # multiple ORed values: using IN is fine
   676             if '' in value:
   714             if '' in value:
   677                 value.remove('')
   715                 value.remove('')
   678                 self._add_not_rel_restr(rel)
   716                 self._add_not_rel_restr(rel)
   679             _add_eid_restr(rel, restrvar, value)
   717             _add_eid_restr(rel, restrvar, value)
   682             if '' in value:
   720             if '' in value:
   683                 value.remove('')
   721                 value.remove('')
   684                 self._add_not_rel_restr(rel)
   722                 self._add_not_rel_restr(rel)
   685             _add_eid_restr(rel, restrvar, value.pop())
   723             _add_eid_restr(rel, restrvar, value.pop())
   686             while value:
   724             while value:
   687                 restrvar, rtrel = _make_relation(self.rqlst, mainvar,
   725                 restrvar, rtrel = _make_relation(self.select, filtered_variable,
   688                                                  self.rtype, self.role)
   726                                                  self.rtype, self.role)
   689                 _add_eid_restr(rel, restrvar, value.pop())
   727                 _add_eid_restr(rel, restrvar, value.pop())
   690 
   728 
   691     @cached
   729     @cached
   692     def _search_card(self, cards):
   730     def _search_card(self, cards):
   718         if not self.no_relation:
   756         if not self.no_relation:
   719             return False
   757             return False
   720         if self._cw.vreg.schema.rschema(self.rtype).final:
   758         if self._cw.vreg.schema.rschema(self.rtype).final:
   721             return False
   759             return False
   722         if self.role == 'object':
   760         if self.role == 'object':
   723             subj = utils.rqlvar_maker(defined=self.rqlst.defined_vars,
   761             subj = utils.rqlvar_maker(defined=self.select.defined_vars,
   724                                       aliases=self.rqlst.aliases).next()
   762                                       aliases=self.select.aliases).next()
   725             obj = self.filtered_variable.name
   763             obj = self.filtered_variable.name
   726         else:
   764         else:
   727             subj = self.filtered_variable.name
   765             subj = self.filtered_variable.name
   728             obj = utils.rqlvar_maker(defined=self.rqlst.defined_vars,
   766             obj = utils.rqlvar_maker(defined=self.select.defined_vars,
   729                                      aliases=self.rqlst.aliases).next()
   767                                      aliases=self.select.aliases).next()
   730         restrictions = []
   768         restrictions = []
   731         if self.rqlst.where:
   769         if self.select.where:
   732             restrictions.append(self.rqlst.where.as_string())
   770             restrictions.append(self.select.where.as_string())
   733         if self.rqlst.with_:
   771         if self.select.with_:
   734             restrictions.append('WITH ' + ','.join(
   772             restrictions.append('WITH ' + ','.join(
   735                 term.as_string() for term in self.rqlst.with_))
   773                 term.as_string() for term in self.select.with_))
   736         if restrictions:
   774         if restrictions:
   737             restrictions = ',' + ','.join(restrictions)
   775             restrictions = ',' + ','.join(restrictions)
   738         else:
   776         else:
   739             restrictions = ''
   777             restrictions = ''
   740         rql = 'Any %s LIMIT 1 WHERE NOT %s %s %s%s' % (
   778         rql = 'Any %s LIMIT 1 WHERE NOT %s %s %s%s' % (
   746             # proper fix
   784             # proper fix
   747             self.exception('cant handle rql generated by %s', self)
   785             self.exception('cant handle rql generated by %s', self)
   748             return False
   786             return False
   749 
   787 
   750     def _add_not_rel_restr(self, rel):
   788     def _add_not_rel_restr(self, rel):
   751         nrrel = nodes.Not(_make_relation(self.rqlst, self.filtered_variable,
   789         nrrel = nodes.Not(_make_relation(self.select, self.filtered_variable,
   752                                          self.rtype, self.role)[1])
   790                                          self.rtype, self.role)[1])
   753         rel.parent.replace(rel, nodes.Or(nrrel, rel))
   791         rel.parent.replace(rel, nodes.Or(nrrel, rel))
   754 
   792 
   755 
   793 
   756 class RelationAttributeFacet(RelationFacet):
   794 class RelationAttributeFacet(RelationFacet):
   812     def add_rql_restrictions(self):
   850     def add_rql_restrictions(self):
   813         """add restriction for this facet into the rql syntax tree"""
   851         """add restriction for this facet into the rql syntax tree"""
   814         value = self._cw.form.get(self.__regid__)
   852         value = self._cw.form.get(self.__regid__)
   815         if not value:
   853         if not value:
   816             return
   854             return
   817         mainvar = self.filtered_variable
   855         filtered_variable = self.filtered_variable
   818         restrvar = _add_rtype_relation(self.rqlst, mainvar, self.rtype,
   856         restrvar = _add_rtype_relation(self.select, filtered_variable, self.rtype,
   819                                        self.role)[0]
   857                                        self.role)[0]
   820         self.rqlst.set_distinct(True)
   858         self.select.set_distinct(True)
   821         if isinstance(value, basestring) or self.operator == 'OR':
   859         if isinstance(value, basestring) or self.operator == 'OR':
   822             # only one value selected or multiple ORed values: using IN is fine
   860             # only one value selected or multiple ORed values: using IN is fine
   823             self.rqlst.add_constant_restriction(
   861             self.select.add_constant_restriction(
   824                 restrvar, self.target_attr, value,
   862                 restrvar, self.target_attr, value,
   825                 self.attrtype, self.comparator)
   863                 self.attrtype, self.comparator)
   826         else:
   864         else:
   827             # multiple values with AND operator
   865             # multiple values with AND operator
   828             self.rqlst.add_constant_restriction(
   866             self.select.add_constant_restriction(
   829                 restrvar, self.target_attr, value.pop(),
   867                 restrvar, self.target_attr, value.pop(),
   830                 self.attrtype, self.comparator)
   868                 self.attrtype, self.comparator)
   831             while value:
   869             while value:
   832                 restrvar = _add_rtype_relation(self.rqlst, mainvar, self.rtype,
   870                 restrvar = _add_rtype_relation(self.select, filtered_variable, self.rtype,
   833                                                self.role)[0]
   871                                                self.role)[0]
   834                 self.rqlst.add_constant_restriction(
   872                 self.select.add_constant_restriction(
   835                     restrvar, self.target_attr, value.pop(),
   873                     restrvar, self.target_attr, value.pop(),
   836                     self.attrtype, self.comparator)
   874                     self.attrtype, self.comparator)
   837 
   875 
   838 
   876 
   839 class AttributeFacet(RelationAttributeFacet):
   877 class AttributeFacet(RelationAttributeFacet):
   881         return True
   919         return True
   882 
   920 
   883     def vocabulary(self):
   921     def vocabulary(self):
   884         """return vocabulary for this facet, eg a list of 2-uple (label, value)
   922         """return vocabulary for this facet, eg a list of 2-uple (label, value)
   885         """
   923         """
   886         rqlst = self.rqlst
   924         select = self.select
   887         rqlst.save_state()
   925         select.save_state()
   888         try:
   926         try:
   889             mainvar = self.filtered_variable
   927             filtered_variable = self.filtered_variable
   890             cleanup_rqlst(rqlst, mainvar)
   928             cleanup_select(select, filtered_variable)
   891             newvar = prepare_vocabulary_rqlst(rqlst, mainvar, self.rtype, self.role)
   929             newvar = prepare_vocabulary_select(select, filtered_variable, self.rtype, self.role)
   892             _set_orderby(rqlst, newvar, self.sortasc, self.sortfunc)
   930             _set_orderby(select, newvar, self.sortasc, self.sortfunc)
   893             try:
   931             try:
   894                 rset = self.rqlexec(rqlst.as_string(), self.cw_rset.args)
   932                 rset = self.rqlexec(select.as_string(), self.cw_rset.args)
   895             except:
   933             except:
   896                 self.exception('error while getting vocabulary for %s, rql: %s',
   934                 self.exception('error while getting vocabulary for %s, rql: %s',
   897                                self, rqlst.as_string())
   935                                self, select.as_string())
   898                 return ()
   936                 return ()
   899         finally:
   937         finally:
   900             rqlst.recover()
   938             select.recover()
   901         # don't call rset_vocabulary on empty result set, it may be an empty
   939         # don't call rset_vocabulary on empty result set, it may be an empty
   902         # *list* (see rqlexec implementation)
   940         # *list* (see rqlexec implementation)
   903         return rset and self.rset_vocabulary(rset)
   941         return rset and self.rset_vocabulary(rset)
   904 
   942 
   905     def support_and(self):
   943     def support_and(self):
   908     def add_rql_restrictions(self):
   946     def add_rql_restrictions(self):
   909         """add restriction for this facet into the rql syntax tree"""
   947         """add restriction for this facet into the rql syntax tree"""
   910         value = self._cw.form.get(self.__regid__)
   948         value = self._cw.form.get(self.__regid__)
   911         if not value:
   949         if not value:
   912             return
   950             return
   913         mainvar = self.filtered_variable
   951         filtered_variable = self.filtered_variable
   914         self.rqlst.add_constant_restriction(mainvar, self.rtype, value,
   952         self.select.add_constant_restriction(filtered_variable, self.rtype, value,
   915                                             self.attrtype, self.comparator)
   953                                             self.attrtype, self.comparator)
   916 
   954 
   917 
   955 
   918 class RangeFacet(AttributeFacet):
   956 class RangeFacet(AttributeFacet):
   919     """This class allows to filter entities according to an attribute of
   957     """This class allows to filter entities according to an attribute of
  1048                                    '%s:%s' % (self.rtype, self),
  1086                                    '%s:%s' % (self.rtype, self),
  1049                                    self._cw.form.get(self.__regid__))
  1087                                    self._cw.form.get(self.__regid__))
  1050 
  1088 
  1051     def add_rql_restrictions(self):
  1089     def add_rql_restrictions(self):
  1052         """add restriction for this facet into the rql syntax tree"""
  1090         """add restriction for this facet into the rql syntax tree"""
  1053         self.rqlst.set_distinct(True) # XXX
  1091         self.select.set_distinct(True) # XXX
  1054         value = self._cw.form.get(self.__regid__)
  1092         value = self._cw.form.get(self.__regid__)
  1055         if not value: # no value sent for this facet
  1093         if not value: # no value sent for this facet
  1056             return
  1094             return
  1057         var = self.rqlst.make_variable()
  1095         var = self.select.make_variable()
  1058         if self.role == 'subject':
  1096         if self.role == 'subject':
  1059             self.rqlst.add_relation(self.filtered_variable, self.rtype, var)
  1097             self.select.add_relation(self.filtered_variable, self.rtype, var)
  1060         else:
  1098         else:
  1061             self.rqlst.add_relation(var, self.rtype, self.filtered_variable)
  1099             self.select.add_relation(var, self.rtype, self.filtered_variable)
  1062 
  1100 
  1063 
  1101 
  1064 ## html widets ################################################################
  1102 ## html widets ################################################################
  1065 
  1103 
  1066 class FacetVocabularyWidget(HTMLWidget):
  1104 class FacetVocabularyWidget(HTMLWidget):
  1129         min: %(minvalue)s,
  1167         min: %(minvalue)s,
  1130         max: %(maxvalue)s,
  1168         max: %(maxvalue)s,
  1131         values: [%(minvalue)s, %(maxvalue)s],
  1169         values: [%(minvalue)s, %(maxvalue)s],
  1132         stop: function(event, ui) { // submit when the user stops sliding
  1170         stop: function(event, ui) { // submit when the user stops sliding
  1133            var form = $('#%(sliderid)s').closest('form');
  1171            var form = $('#%(sliderid)s').closest('form');
  1134            buildRQL.apply(null, evalJSON(form.attr('cubicweb:facetargs')));
  1172            buildRQL.apply(null, cw.evalJSON(form.attr('cubicweb:facetargs')));
  1135         },
  1173         },
  1136         slide: function(event, ui) {
  1174         slide: function(event, ui) {
  1137             jQuery('#%(sliderid)s_inf').html(_formatter(ui.values[0]));
  1175             jQuery('#%(sliderid)s_inf').html(_formatter(ui.values[0]));
  1138             jQuery('#%(sliderid)s_sup').html(_formatter(ui.values[1]));
  1176             jQuery('#%(sliderid)s_sup').html(_formatter(ui.values[1]));
  1139             jQuery('input[name=%(facetid)s_inf]').val(ui.values[0]);
  1177             jQuery('input[name=%(facetid)s_inf]').val(ui.values[0]);
  1284     """called by javascript to get a rql string from filter form"""
  1322     """called by javascript to get a rql string from filter form"""
  1285 
  1323 
  1286     def __init__(self, req):
  1324     def __init__(self, req):
  1287         self._cw = req
  1325         self._cw = req
  1288 
  1326 
  1289     def build_rql(self):#, tablefilter=False):
  1327     def build_rql(self):
  1290         form = self._cw.form
  1328         form = self._cw.form
  1291         facetids = form['facets'].split(',')
  1329         facetids = form['facets'].split(',')
  1292         # XXX Union unsupported yet
  1330         # XXX Union unsupported yet
  1293         select = self._cw.vreg.parse(self._cw, form['baserql']).children[0]
  1331         select = self._cw.vreg.parse(self._cw, form['baserql']).children[0]
  1294         mainvar = filtered_variable(select)
  1332         filtered_variable = get_filtered_variable(select, form.get('mainvar'))
  1295         toupdate = []
  1333         toupdate = []
  1296         for facetid in facetids:
  1334         for facetid in facetids:
  1297             facet = get_facet(self._cw, facetid, select, mainvar)
  1335             facet = get_facet(self._cw, facetid, select, filtered_variable)
  1298             facet.add_rql_restrictions()
  1336             facet.add_rql_restrictions()
  1299             if facet.needs_update:
  1337             if facet.needs_update:
  1300                 toupdate.append(facetid)
  1338                 toupdate.append(facetid)
  1301         return select.as_string(), toupdate
  1339         return select.as_string(), toupdate