web/facet.py
changeset 7618 5395007c415c
parent 7615 e5ad51352eb3
child 7624 ce020f90fb8e
equal deleted inserted replaced
7617:be5f68f9314e 7618:5395007c415c
    28 --------------------------
    28 --------------------------
    29 .. autoclass:: cubicweb.web.facet.RelationFacet
    29 .. autoclass:: cubicweb.web.facet.RelationFacet
    30 .. autoclass:: cubicweb.web.facet.RelationAttributeFacet
    30 .. autoclass:: cubicweb.web.facet.RelationAttributeFacet
    31 .. autoclass:: cubicweb.web.facet.HasRelationFacet
    31 .. autoclass:: cubicweb.web.facet.HasRelationFacet
    32 .. autoclass:: cubicweb.web.facet.AttributeFacet
    32 .. autoclass:: cubicweb.web.facet.AttributeFacet
       
    33 .. autoclass:: cubicweb.web.facet.RQLPathFacet
    33 .. autoclass:: cubicweb.web.facet.RangeFacet
    34 .. autoclass:: cubicweb.web.facet.RangeFacet
    34 .. autoclass:: cubicweb.web.facet.DateRangeFacet
    35 .. autoclass:: cubicweb.web.facet.DateRangeFacet
    35 
    36 
    36 Classes for facets implementor
    37 Classes for facets implementor
    37 ------------------------------
    38 ------------------------------
   148     * add the new variable to GROUPBY clause if necessary
   149     * add the new variable to GROUPBY clause if necessary
   149     * add the new variable to the selection
   150     * add the new variable to the selection
   150     """
   151     """
   151     newvar = _add_rtype_relation(select, filtered_variable, rtype, role)[0]
   152     newvar = _add_rtype_relation(select, filtered_variable, rtype, role)[0]
   152     if select_target_entity:
   153     if select_target_entity:
   153         if select.groupby:
   154         # if select.groupby: XXX we remove groupby now
   154             select.add_group_var(newvar)
   155         #     select.add_group_var(newvar)
   155         select.add_selected(newvar)
   156         select.add_selected(newvar)
   156     # add is restriction if necessary
   157     # add is restriction if necessary
   157     if filtered_variable.stinfo['typerel'] is None:
   158     if filtered_variable.stinfo['typerel'] is None:
   158         etypes = frozenset(sol[filtered_variable.name] for sol in select.solutions)
   159         etypes = frozenset(sol[filtered_variable.name] for sol in select.solutions)
   159         select.add_type_restriction(filtered_variable, etypes)
   160         select.add_type_restriction(filtered_variable, etypes)
   298     """
   299     """
   299     newvar, newrel = _make_relation(select, variable, rtype, role)
   300     newvar, newrel = _make_relation(select, variable, rtype, role)
   300     select.add_restriction(newrel)
   301     select.add_restriction(newrel)
   301     return newvar, newrel
   302     return newvar, newrel
   302 
   303 
   303 def _add_eid_restr(rel, restrvar, value):
       
   304     rrel = nodes.make_constant_restriction(restrvar, 'eid', value, 'Int')
       
   305     rel.parent.replace(rel, nodes.And(rel, rrel))
       
   306 
       
   307 def _remove_relation(select, rel, var):
   304 def _remove_relation(select, rel, var):
   308     """remove a constraint relation from the syntax tree"""
   305     """remove a constraint relation from the syntax tree"""
   309     # remove the relation
   306     # remove the relation
   310     select.remove_node(rel)
   307     select.remove_node(rel)
   311     # remove relations where the filtered variable appears on the
   308     # remove relations where the filtered variable appears on the
   328         vref.register_reference()
   325         vref.register_reference()
   329         sortfunc = nodes.Function(sortfuncname)
   326         sortfunc = nodes.Function(sortfuncname)
   330         sortfunc.append(vref)
   327         sortfunc.append(vref)
   331         term = nodes.SortTerm(sortfunc, sortasc)
   328         term = nodes.SortTerm(sortfunc, sortasc)
   332         select.add_sort_term(term)
   329         select.add_sort_term(term)
       
   330 
       
   331 def _get_var(select, varname, varmap):
       
   332     try:
       
   333         return varmap[varname]
       
   334     except KeyError:
       
   335         varmap[varname] = var = select.make_variable()
       
   336         return var
   333 
   337 
   334 
   338 
   335 _prepare_vocabulary_rqlst = deprecated('[3.13] renamed prepare_vocabulary_select')(
   339 _prepare_vocabulary_rqlst = deprecated('[3.13] renamed prepare_vocabulary_select')(
   336     prepare_vocabulary_select)
   340     prepare_vocabulary_select)
   337 _cleanup_rqlst = deprecated('[3.13] renamed to cleanup_select')(cleanup_select)
   341 _cleanup_rqlst = deprecated('[3.13] renamed to cleanup_select')(cleanup_select)
   402         self.filtered_variable = filtered_variable
   406         self.filtered_variable = filtered_variable
   403 
   407 
   404     def __repr__(self):
   408     def __repr__(self):
   405         return '<%s>' % self.__class__.__name__
   409         return '<%s>' % self.__class__.__name__
   406 
   410 
       
   411     def get_widget(self):
       
   412         """Return the widget instance to use to display this facet, or None if
       
   413         the facet can't do anything valuable (only one value in the vocabulary
       
   414         for instance).
       
   415         """
       
   416         raise NotImplementedError
       
   417 
       
   418     def add_rql_restrictions(self):
       
   419         """When some facet criteria has been updated, this method is called to
       
   420         add restriction for this facet into the rql syntax tree. It should get
       
   421         back its value in form parameters, and modify the syntax tree
       
   422         (`self.select`) accordingly.
       
   423         """
       
   424         raise NotImplementedError
       
   425 
   407     @property
   426     @property
   408     def operator(self):
   427     def operator(self):
   409         """Return the operator (AND or OR) to use for this facet when multiple
   428         """Return the operator (AND or OR) to use for this facet when multiple
   410         values are selected.
   429         values are selected.
   411         """
   430         """
   418         """
   437         """
   419         try:
   438         try:
   420             return self._cw.execute(rql, args)
   439             return self._cw.execute(rql, args)
   421         except Unauthorized:
   440         except Unauthorized:
   422             return []
   441             return []
   423 
       
   424     def get_widget(self):
       
   425         """Return the widget instance to use to display this facet, or None if
       
   426         the facet can't do anything valuable (only one value in the vocabulary
       
   427         for instance).
       
   428         """
       
   429         raise NotImplementedError
       
   430 
       
   431     def add_rql_restrictions(self):
       
   432         """When some facet criteria has been updated, this method is called to
       
   433         add restriction for this facet into the rql syntax tree. It should get
       
   434         back its value in form parameters, and modify the syntax tree
       
   435         (`self.select`) accordingly.
       
   436         """
       
   437         raise NotImplementedError
       
   438 
   442 
   439     @property
   443     @property
   440     def wdgclass(self):
   444     def wdgclass(self):
   441         raise NotImplementedError
   445         raise NotImplementedError
   442 
   446 
   513 
   517 
   514     When no `label_vid` is set, you will get translated value if `i18nable` is
   518     When no `label_vid` is set, you will get translated value if `i18nable` is
   515     set. By default, `i18nable` will be set according to the schema, but you can
   519     set. By default, `i18nable` will be set according to the schema, but you can
   516     force its value by setting it has a class attribute.
   520     force its value by setting it has a class attribute.
   517 
   521 
   518     You can filter out target entity types by specifying `target_type`
   522     You can filter out target entity types by specifying `target_type`.
   519 
   523 
   520     By default, vocabulary will be displayed sorted on `target_attr` value in an
   524     By default, vocabulary will be displayed sorted on `target_attr` value in an
   521     ascending way. You can control sorting with:
   525     ascending way. You can control sorting with:
   522 
   526 
   523     * `sortfunc`: set this to a stored procedure name if you want to sort on the
   527     * `sortfunc`: set this to a stored procedure name if you want to sort on the
   559     """
   563     """
   560     __select__ = partial_relation_possible() & match_context_prop()
   564     __select__ = partial_relation_possible() & match_context_prop()
   561     # class attributes to configure the relation facet
   565     # class attributes to configure the relation facet
   562     rtype = None
   566     rtype = None
   563     role = 'subject'
   567     role = 'subject'
       
   568     target_type = None
   564     target_attr = 'eid'
   569     target_attr = 'eid'
   565     target_type = None
   570     # for subclasses parametrization, should not change if you want a
       
   571     # RelationFacet
       
   572     target_attr_type = 'Int'
       
   573     restr_attr = 'eid'
       
   574     restr_attr_type = 'Int'
       
   575     comparator = '=' # could be '<', '<=', '>', '>='
   566     # set this to a stored procedure name if you want to sort on the result of
   576     # set this to a stored procedure name if you want to sort on the result of
   567     # this function's result instead of direct value
   577     # this function's result instead of direct value
   568     sortfunc = None
   578     sortfunc = None
   569     # ascendant/descendant sorting
   579     # ascendant/descendant sorting
   570     sortasc = True
   580     sortasc = True
   598                 self._select_target_entity)
   608                 self._select_target_entity)
   599             if self.target_type is not None:
   609             if self.target_type is not None:
   600                 select.add_type_restriction(var, self.target_type)
   610                 select.add_type_restriction(var, self.target_type)
   601             try:
   611             try:
   602                 rset = self.rqlexec(select.as_string(), self.cw_rset.args)
   612                 rset = self.rqlexec(select.as_string(), self.cw_rset.args)
   603             except:
   613             except Exception:
   604                 self.exception('error while getting vocabulary for %s, rql: %s',
   614                 self.exception('error while getting vocabulary for %s, rql: %s',
   605                                self, select.as_string())
   615                                self, select.as_string())
   606                 return ()
   616                 return ()
   607         finally:
   617         finally:
   608             select.recover()
   618             select.recover()
   624             if self._select_target_entity:
   634             if self._select_target_entity:
   625                 prepare_vocabulary_select(select, self.filtered_variable, self.rtype,
   635                 prepare_vocabulary_select(select, self.filtered_variable, self.rtype,
   626                                          self.role, select_target_entity=True)
   636                                          self.role, select_target_entity=True)
   627             else:
   637             else:
   628                 insert_attr_select_relation(
   638                 insert_attr_select_relation(
   629                     select, self.filtered_variable, self.rtype, self.role, self.target_attr,
   639                     select, self.filtered_variable, self.rtype, self.role,
   630                     select_target_entity=False)
   640                     self.target_attr, select_target_entity=False)
   631             values = [unicode(x) for x, in self.rqlexec(select.as_string())]
   641             values = [unicode(x) for x, in self.rqlexec(select.as_string())]
   632         except:
   642         except Exception:
   633             self.exception('while computing values for %s', self)
   643             self.exception('while computing values for %s', self)
   634             return []
   644             return []
   635         finally:
   645         finally:
   636             select.recover()
   646             select.recover()
   637         if self._include_no_relation():
   647         if self._include_no_relation():
   706                  DeprecationWarning)
   716                  DeprecationWarning)
   707             support = support()
   717             support = support()
   708         return support
   718         return support
   709 
   719 
   710     def value_restriction(self, restrvar, rel, value):
   720     def value_restriction(self, restrvar, rel, value):
       
   721         if self.restr_attr != 'eid':
       
   722             self.select.set_distinct(True)
   711         if isinstance(value, basestring):
   723         if isinstance(value, basestring):
   712             # only one value selected
   724             # only one value selected
   713             if value:
   725             if value:
   714                 self.select.add_eid_restriction(restrvar, value)
   726                 self.select.add_constant_restriction(
       
   727                     restrvar, self.restr_attr, value,
       
   728                     self.restr_attr_type)
   715             else:
   729             else:
   716                 rel.parent.replace(rel, nodes.Not(rel))
   730                 rel.parent.replace(rel, nodes.Not(rel))
   717         elif self.operator == 'OR':
   731         elif self.operator == 'OR':
   718             # set_distinct only if rtype cardinality is > 1
   732             # set_distinct only if rtype cardinality is > 1
   719             if self._support_and_compat():
   733             if self._support_and_compat():
   720                 self.select.set_distinct(True)
   734                 self.select.set_distinct(True)
   721             # multiple ORed values: using IN is fine
   735             # multiple ORed values: using IN is fine
   722             if '' in value:
   736             if '' in value:
   723                 value.remove('')
   737                 value.remove('')
   724                 self._add_not_rel_restr(rel)
   738                 self._add_not_rel_restr(rel)
   725             _add_eid_restr(rel, restrvar, value)
   739             self._and_restriction(rel, restrvar, value)
   726         else:
   740         else:
   727             # multiple values with AND operator
   741             # multiple values with AND operator
   728             if '' in value:
   742             if '' in value:
   729                 value.remove('')
   743                 value.remove('')
   730                 self._add_not_rel_restr(rel)
   744                 self._add_not_rel_restr(rel)
   731             _add_eid_restr(rel, restrvar, value.pop())
   745             self._and_restriction(rel, restrvar, value.pop())
   732             while value:
   746             while value:
   733                 restrvar, rtrel = _make_relation(self.select, filtered_variable,
   747                 restrvar, rtrel = _make_relation(self.select, filtered_variable,
   734                                                  self.rtype, self.role)
   748                                                  self.rtype, self.role)
   735                 _add_eid_restr(rel, restrvar, value.pop())
   749                 self._and_restriction(rel, restrvar, value.pop())
       
   750 
       
   751     def _and_restriction(self, rel, restrvar, value):
       
   752         rrel = nodes.make_constant_restriction(restrvar, self.restr_attr,
       
   753                                                value, self.restr_attr_type)
       
   754         rel.parent.replace(rel, nodes.And(rel, rrel))
       
   755 
   736 
   756 
   737     @cached
   757     @cached
   738     def _search_card(self, cards):
   758     def _search_card(self, cards):
   739         for rdef in self._iter_rdefs():
   759         for rdef in self._iter_rdefs():
   740             if rdef.role_cardinality(self.role) in cards:
   760             if rdef.role_cardinality(self.role) in cards:
   785             restrictions = ''
   805             restrictions = ''
   786         rql = 'Any %s LIMIT 1 WHERE NOT %s %s %s%s' % (
   806         rql = 'Any %s LIMIT 1 WHERE NOT %s %s %s%s' % (
   787             self.filtered_variable.name, subj, self.rtype, obj, restrictions)
   807             self.filtered_variable.name, subj, self.rtype, obj, restrictions)
   788         try:
   808         try:
   789             return bool(self.rqlexec(rql, self.cw_rset and self.cw_rset.args))
   809             return bool(self.rqlexec(rql, self.cw_rset and self.cw_rset.args))
   790         except:
   810         except Exception:
   791             # catch exception on executing rql, work-around #1356884 until a
   811             # catch exception on executing rql, work-around #1356884 until a
   792             # proper fix
   812             # proper fix
   793             self.exception('cant handle rql generated by %s', self)
   813             self.exception('cant handle rql generated by %s', self)
   794             return False
   814             return False
   795 
   815 
   804     entities to which they are related. Most things work similarly as
   824     entities to which they are related. Most things work similarly as
   805     :class:`RelationFacet`, except that:
   825     :class:`RelationFacet`, except that:
   806 
   826 
   807     * `label_vid` doesn't make sense here
   827     * `label_vid` doesn't make sense here
   808 
   828 
   809     * you should specify the attribute type using `attrtype` if it's not a
   829     * you should specify the attribute type using `target_attr_type` if it's not a
   810       String
   830       String
   811 
   831 
   812     * you can specify a comparison operator using `comparator`
   832     * you can specify a comparison operator using `comparator`
   813 
   833 
   814 
   834 
   837           # we want to search according to address 'postal_code' attribute
   857           # we want to search according to address 'postal_code' attribute
   838           target_attr = 'postalcode'
   858           target_attr = 'postalcode'
   839     """
   859     """
   840     _select_target_entity = False
   860     _select_target_entity = False
   841     # attribute type
   861     # attribute type
   842     attrtype = 'String'
   862     target_attr_type = 'String'
   843     # type of comparison: default is an exact match on the attribute value
   863     # type of comparison: default is an exact match on the attribute value
   844     comparator = '=' # could be '<', '<=', '>', '>='
   864     comparator = '=' # could be '<', '<=', '>', '>='
       
   865 
       
   866     @property
       
   867     def restr_attr(self):
       
   868         return self.target_attr
       
   869 
       
   870     @property
       
   871     def restr_attr_type(self):
       
   872         return self.target_attr_type
   845 
   873 
   846     def rset_vocabulary(self, rset):
   874     def rset_vocabulary(self, rset):
   847         if self.i18nable:
   875         if self.i18nable:
   848             _ = self._cw._
   876             _ = self._cw._
   849         else:
   877         else:
   852             return [(_(value), value) for value, in rset]
   880             return [(_(value), value) for value, in rset]
   853         values = [(_(value), value) for value, in rset]
   881         values = [(_(value), value) for value, in rset]
   854         if self.sortasc:
   882         if self.sortasc:
   855             return sorted(values)
   883             return sorted(values)
   856         return reversed(sorted(values))
   884         return reversed(sorted(values))
   857 
       
   858     def add_rql_restrictions(self):
       
   859         """add restriction for this facet into the rql syntax tree"""
       
   860         value = self._cw.form.get(self.__regid__)
       
   861         if not value:
       
   862             return
       
   863         filtered_variable = self.filtered_variable
       
   864         restrvar = _add_rtype_relation(self.select, filtered_variable, self.rtype,
       
   865                                        self.role)[0]
       
   866         self.select.set_distinct(True)
       
   867         if isinstance(value, basestring) or self.operator == 'OR':
       
   868             # only one value selected or multiple ORed values: using IN is fine
       
   869             self.select.add_constant_restriction(
       
   870                 restrvar, self.target_attr, value,
       
   871                 self.attrtype, self.comparator)
       
   872         else:
       
   873             # multiple values with AND operator
       
   874             self.select.add_constant_restriction(
       
   875                 restrvar, self.target_attr, value.pop(),
       
   876                 self.attrtype, self.comparator)
       
   877             while value:
       
   878                 restrvar = _add_rtype_relation(self.select, filtered_variable, self.rtype,
       
   879                                                self.role)[0]
       
   880                 self.select.add_constant_restriction(
       
   881                     restrvar, self.target_attr, value.pop(),
       
   882                     self.attrtype, self.comparator)
       
   883 
   885 
   884 
   886 
   885 class AttributeFacet(RelationAttributeFacet):
   887 class AttributeFacet(RelationAttributeFacet):
   886     """Base facet to filter some entities according one of their attribute.
   888     """Base facet to filter some entities according one of their attribute.
   887     Configuration is mostly similarly as :class:`RelationAttributeFacet`, except that:
   889     Configuration is mostly similarly as :class:`RelationAttributeFacet`, except that:
   937             cleanup_select(select, filtered_variable)
   939             cleanup_select(select, filtered_variable)
   938             newvar = prepare_vocabulary_select(select, filtered_variable, self.rtype, self.role)
   940             newvar = prepare_vocabulary_select(select, filtered_variable, self.rtype, self.role)
   939             _set_orderby(select, newvar, self.sortasc, self.sortfunc)
   941             _set_orderby(select, newvar, self.sortasc, self.sortfunc)
   940             try:
   942             try:
   941                 rset = self.rqlexec(select.as_string(), self.cw_rset.args)
   943                 rset = self.rqlexec(select.as_string(), self.cw_rset.args)
   942             except:
   944             except Exception:
   943                 self.exception('error while getting vocabulary for %s, rql: %s',
   945                 self.exception('error while getting vocabulary for %s, rql: %s',
   944                                self, select.as_string())
   946                                self, select.as_string())
   945                 return ()
   947                 return ()
   946         finally:
   948         finally:
   947             select.recover()
   949             select.recover()
   954         value = self._cw.form.get(self.__regid__)
   956         value = self._cw.form.get(self.__regid__)
   955         if not value:
   957         if not value:
   956             return
   958             return
   957         filtered_variable = self.filtered_variable
   959         filtered_variable = self.filtered_variable
   958         self.select.add_constant_restriction(filtered_variable, self.rtype, value,
   960         self.select.add_constant_restriction(filtered_variable, self.rtype, value,
   959                                             self.attrtype, self.comparator)
   961                                             self.target_attr_type, self.comparator)
       
   962 
       
   963 
       
   964 class RQLPathFacet(RelationFacet):
       
   965     """Base facet to filter some entities according to an arbitrary rql
       
   966     path. Path should be specified as a list of 3-uples or triplet string, where
       
   967     'X' represent the filtered variable. You should specify using
       
   968     `filter_variable` the snippet variable that will be used to filter out
       
   969     results. You may also specify a `label_variable`. If you want to filter on
       
   970     an attribute value, you usually don't want to specify the later since it's
       
   971     the same as the filter variable, though you may have to specify the attribute
       
   972     type using `restr_attr_type` if there are some type ambiguity in the schema
       
   973     for the attribute.
       
   974 
       
   975     Using this facet, we can rewrite facets we defined previously:
       
   976 
       
   977     .. sourcecode:: python
       
   978 
       
   979       class AgencyFacet(RQLPathFacet):
       
   980           __regid__ = 'agency'
       
   981           # this facet should only be selected when visualizing offices
       
   982           __select__ = RelationFacet.__select__ & is_instance('Office')
       
   983           # this facet is a filter on the 'Agency' entities linked to the office
       
   984           # through the 'proposed_by' relation, where the office is the subject
       
   985           # of the relation
       
   986           path = ['X has_address O', 'O name N']
       
   987           filter_variable = 'O'
       
   988           label_variable = 'N'
       
   989 
       
   990       class PostalCodeFacet(RQLPathFacet):
       
   991           __regid__ = 'postalcode'
       
   992           # this facet should only be selected when visualizing offices
       
   993           __select__ = RelationAttributeFacet.__select__ & is_instance('Office')
       
   994           # this facet is a filter on the PostalAddress entities linked to the
       
   995           # office through the 'has_address' relation, where the office is the
       
   996           # subject of the relation
       
   997           path = ['X has_address O', 'O postal_code PC']
       
   998           filter_variable = 'PC'
       
   999 
       
  1000     Though some features, such as 'no value' or automatic internationalization,
       
  1001     won't work. This facet class is designed to be used for cases where
       
  1002     :class:`RelationFacet` or :class:`RelationAttributeFacet` can't do the trick
       
  1003     (e.g when you want to filter on entities where are not directly linked to
       
  1004      the filtered entities).
       
  1005     """
       
  1006     # must be specified
       
  1007     path = None
       
  1008     filter_variable = None
       
  1009     # may be specified
       
  1010     label_variable = None
       
  1011     # usually guessed, but may be explicitly specified
       
  1012     restr_attr = None
       
  1013     restr_attr_type = None
       
  1014 
       
  1015     # XXX disabled features
       
  1016     i18nable = False
       
  1017     no_relation = False
       
  1018     support_and = False
       
  1019 
       
  1020     def __init__(self, *args, **kwargs):
       
  1021         super(RQLPathFacet, self).__init__(*args, **kwargs)
       
  1022         assert self.path and isinstance(self.path, (list, tuple)), \
       
  1023                'path should be a list of 3-uples, not %s' % self.path
       
  1024         for part in self.path:
       
  1025             if isinstance(part, basestring):
       
  1026                 part = part.split()
       
  1027             assert len(part) == 3, \
       
  1028                    'path should be a list of 3-uples, not %s' % part
       
  1029 
       
  1030     def __repr__(self):
       
  1031         return '<%s %s>' % (self.__class__.__name__,
       
  1032                             ','.join(str(p) for p in self.path))
       
  1033 
       
  1034     def vocabulary(self):
       
  1035         """return vocabulary for this facet, eg a list of 2-uple (label, value)
       
  1036         """
       
  1037         select = self.select
       
  1038         select.save_state()
       
  1039         if self.rql_sort:
       
  1040             sort = self.sortasc
       
  1041         else:
       
  1042             sort = None # will be sorted on label
       
  1043         try:
       
  1044             cleanup_select(select, self.filtered_variable)
       
  1045             varmap, restrvar = self.add_path_to_select()
       
  1046             select.append_selected(nodes.VariableRef(restrvar))
       
  1047             if self.label_variable:
       
  1048                 attrvar = varmap[self.label_variable]
       
  1049             else:
       
  1050                 attrvar = restrvar
       
  1051             select.append_selected(nodes.VariableRef(attrvar))
       
  1052             if sort is not None:
       
  1053                 _set_orderby(select, attrvar, sort, self.sortfunc)
       
  1054             try:
       
  1055                 rset = self.rqlexec(select.as_string(), self.cw_rset.args)
       
  1056             except Exception:
       
  1057                 self.exception('error while getting vocabulary for %s, rql: %s',
       
  1058                                self, select.as_string())
       
  1059                 return ()
       
  1060         finally:
       
  1061             select.recover()
       
  1062         # don't call rset_vocabulary on empty result set, it may be an empty
       
  1063         # *list* (see rqlexec implementation)
       
  1064         values = rset and self.rset_vocabulary(rset) or []
       
  1065         if self._include_no_relation():
       
  1066             values.insert(0, (self._cw._(self.no_relation_label), ''))
       
  1067         return values
       
  1068 
       
  1069     def possible_values(self):
       
  1070         """return a list of possible values (as string since it's used to
       
  1071         compare to a form value in javascript) for this facet
       
  1072         """
       
  1073         select = self.select
       
  1074         select.save_state()
       
  1075         try:
       
  1076             cleanup_select(select, self.filtered_variable)
       
  1077             varmap, restrvar = self.add_path_to_select(skiplabel=True)
       
  1078             select.append_selected(nodes.VariableRef(restrvar))
       
  1079             values = [unicode(x) for x, in self.rqlexec(select.as_string())]
       
  1080         except Exception:
       
  1081             self.exception('while computing values for %s', self)
       
  1082             return []
       
  1083         finally:
       
  1084             select.recover()
       
  1085         if self._include_no_relation():
       
  1086             values.append('')
       
  1087         return values
       
  1088 
       
  1089     def add_rql_restrictions(self):
       
  1090         """add restriction for this facet into the rql syntax tree"""
       
  1091         value = self._cw.form.get(self.__regid__)
       
  1092         if value is None:
       
  1093             return
       
  1094         varmap, restrvar = self.add_path_to_select(
       
  1095             skiplabel=True, skipattrfilter=True)
       
  1096         self.value_restriction(restrvar, None, value)
       
  1097 
       
  1098     def add_path_to_select(self, skiplabel=False, skipattrfilter=False):
       
  1099         varmap = {'X': self.filtered_variable}
       
  1100         actual_filter_variable = None
       
  1101         for part in self.path:
       
  1102             if isinstance(part, basestring):
       
  1103                 part = part.split()
       
  1104             subject, rtype, object = part
       
  1105             if skiplabel and object == self.label_variable:
       
  1106                 continue
       
  1107             if object == self.filter_variable:
       
  1108                 rschema = self._cw.vreg.schema.rschema(rtype)
       
  1109                 if rschema.final:
       
  1110                     # filter variable is an attribute variable
       
  1111                     if self.restr_attr is None:
       
  1112                         self.restr_attr = rtype
       
  1113                     if self.restr_attr_type is None:
       
  1114                         attrtypes = set(obj for subj,obj in rschema.rdefs)
       
  1115                         if len(attrtypes) > 1:
       
  1116                             raise Exception('ambigous attribute %s, specify attrtype on %s'
       
  1117                                             % (rtype, self.__class__))
       
  1118                         self.restr_attr_type = iter(attrtypes).next()
       
  1119                     if skipattrfilter:
       
  1120                         actual_filter_variable = subject
       
  1121                         continue
       
  1122             subjvar = _get_var(self.select, subject, varmap)
       
  1123             objvar = _get_var(self.select, object, varmap)
       
  1124             rel = nodes.make_relation(subjvar, rtype, (objvar,),
       
  1125                                       nodes.VariableRef)
       
  1126             self.select.add_restriction(rel)
       
  1127         if self.restr_attr is None:
       
  1128             self.restr_attr = 'eid'
       
  1129         if self.restr_attr_type is None:
       
  1130             self.restr_attr_type = 'Int'
       
  1131         if actual_filter_variable:
       
  1132             restrvar = varmap[actual_filter_variable]
       
  1133         else:
       
  1134             restrvar = varmap[self.filter_variable]
       
  1135         return varmap, restrvar
   960 
  1136 
   961 
  1137 
   962 class RangeFacet(AttributeFacet):
  1138 class RangeFacet(AttributeFacet):
   963     """This class allows to filter entities according to an attribute of
  1139     """This class allows to filter entities according to an attribute of
   964     numerical type.
  1140     numerical type.
   986 
  1162 
   987     .. image:: ../images/facet_range.png
  1163     .. image:: ../images/facet_range.png
   988 
  1164 
   989     .. _jquery: http://www.jqueryui.com/
  1165     .. _jquery: http://www.jqueryui.com/
   990     """
  1166     """
   991     attrtype = 'Float' # only numerical types are supported
  1167     target_attr_type = 'Float' # only numerical types are supported
   992     needs_update = False # not supported actually
  1168     needs_update = False # not supported actually
   993 
  1169 
   994     @property
  1170     @property
   995     def wdgclass(self):
  1171     def wdgclass(self):
   996         return FacetRangeWidget
  1172         return FacetRangeWidget
  1031 
  1207 
  1032     def _add_restriction(self, value, operator):
  1208     def _add_restriction(self, value, operator):
  1033         self.select.add_constant_restriction(self.filtered_variable,
  1209         self.select.add_constant_restriction(self.filtered_variable,
  1034                                              self.rtype,
  1210                                              self.rtype,
  1035                                              self.formatvalue(value),
  1211                                              self.formatvalue(value),
  1036                                              self.attrtype, operator)
  1212                                              self.target_attr_type, operator)
  1037 
  1213 
  1038 
  1214 
  1039 
  1215 
  1040 class DateRangeFacet(RangeFacet):
  1216 class DateRangeFacet(RangeFacet):
  1041     """This class works similarly as the :class:`RangeFacet` but for attribute
  1217     """This class works similarly as the :class:`RangeFacet` but for attribute
  1043 
  1219 
  1044     The image below display the rendering of the slider for a date range:
  1220     The image below display the rendering of the slider for a date range:
  1045 
  1221 
  1046     .. image:: ../images/facet_date_range.png
  1222     .. image:: ../images/facet_date_range.png
  1047     """
  1223     """
  1048     attrtype = 'Date' # only date types are supported
  1224     target_attr_type = 'Date' # only date types are supported
  1049 
  1225 
  1050     @property
  1226     @property
  1051     def wdgclass(self):
  1227     def wdgclass(self):
  1052         return DateFacetRangeWidget
  1228         return DateFacetRangeWidget
  1053 
  1229