The facets system

Facets allow to restrict searches according to some user friendly criterias. CubicWeb has a builtin facet system to define restrictions filters really as easily as possible.

Here is an exemple of the facets rendering picked from our http://www.cubicweb.org web site:

../../../_images/facet_overview.png

Facets will appear on each page presenting more than one entity that may be filtered according to some known criteria.

Base classes for facets

The cubicweb.web.facet module contains a set of abstract classes to use as bases to build your own facets

All facet classes inherits from the AbstractFacet class, though you’ll usually find some more handy class that do what you want.

Let’s see available classes.

Classes you’ll want to use

class cubicweb.web.facet.RelationFacet(req, select=None, filtered_variable=None, **kwargs)[source]

Base facet to filter some entities according to other entities to which they are related. Create concrete facet by inheriting from this class an then configuring it by setting class attribute described below.

The relation is defined by the rtype and role attributes.

The no_relation boolean flag tells if a special ‘no relation’ value should be added (allowing to filter on entities which do not have the relation set). Default is computed according the relation’s cardinality.

The values displayed for related entities will be:

  • result of calling their label_vid view if specified

  • else their target_attr attribute value if specified

  • else their eid (you usually want something nicer…)

When no label_vid is set, you will get translated value if i18nable is set. By default, i18nable will be set according to the schema, but you can force its value by setting it has a class attribute.

You can filter out target entity types by specifying target_type.

By default, vocabulary will be displayed sorted on target_attr value in an ascending way. You can control sorting with:

  • sortfunc: set this to a stored procedure name if you want to sort on the result of this function’s result instead of direct value

  • sortasc: boolean flag to control ascendant/descendant sorting

To illustrate this facet, let’s take for example an excerpt of the schema of an office location search application:

class Office(WorkflowableEntityType):
    price = Int(description='euros / m2 / HC / HT')
    surface = Int(description='m2')
    has_address = SubjectRelation('PostalAddress',
                                  cardinality='1?',
                                  composite='subject')
    proposed_by = SubjectRelation('Agency')

We can simply define a facet to filter offices according to the agency proposing it:

class AgencyFacet(RelationFacet):
    __regid__ = 'agency'
    # this facet should only be selected when visualizing offices
    __select__ = RelationFacet.__select__ & is_instance('Office')
    # this facet is a filter on the 'Agency' entities linked to the office
    # through the 'proposed_by' relation, where the office is the subject
    # of the relation
    rtype = 'has_address'
    # 'subject' is the default but setting it explicitly doesn't hurt...
    role = 'subject'
    # we want to display the agency's name
    target_attr = 'name'
class cubicweb.web.facet.RelationAttributeFacet(req, select=None, filtered_variable=None, **kwargs)[source]

Base facet to filter some entities according to an attribute of other entities to which they are related. Most things work similarly as RelationFacet, except that:

  • label_vid doesn’t make sense here

  • you should specify the attribute type using target_attr_type if it’s not a String

  • you can specify a comparison operator using comparator

Back to our example… if you want to search office by postal code and that you use a RelationFacet for that, you won’t get the expected behaviour: if two offices have the same postal code, they’ve however two different addresses. So you’ll see in the facet the same postal code twice, though linked to a different address entity. There is a great chance your users won’t understand that…

That’s where this class come in! It’s used to said that you want to filter according to the attribute value of a relatied entity, not to the entity itself. Now here is the source code for the facet:

class PostalCodeFacet(RelationAttributeFacet):
    __regid__ = 'postalcode'
    # this facet should only be selected when visualizing offices
    __select__ = RelationAttributeFacet.__select__ & is_instance('Office')
    # this facet is a filter on the PostalAddress entities linked to the
    # office through the 'has_address' relation, where the office is the
    # subject of the relation
    rtype = 'has_address'
    role = 'subject'
    # we want to search according to address 'postal_code' attribute
    target_attr = 'postalcode'
class cubicweb.web.facet.HasRelationFacet(req, select=None, filtered_variable=None, **kwargs)[source]

This class simply filter according to the presence of a relation (whatever the entity at the other end). It display a simple checkbox that lets you refine your selection in order to get only entities that actually have this relation. You simply have to define which relation using the rtype and role attributes.

Here is an example of the rendering of thos facet to filter book with image and the corresponding code:

../../../_images/facet_has_image.png
class HasImageFacet(HasRelationFacet):
    __regid__ = 'hasimage'
    __select__ = HasRelationFacet.__select__ & is_instance('Book')
    rtype = 'has_image'
    role = 'subject'
class cubicweb.web.facet.AttributeFacet(req, select=None, filtered_variable=None, **kwargs)[source]

Base facet to filter some entities according one of their attribute. Configuration is mostly similarly as RelationAttributeFacet, except that:

  • target_attr doesn’t make sense here (you specify the attribute using rtype

  • role neither, it’s systematically ‘subject’

So, suppose that in our office search example you want to refine search according to the office’s surface. Here is a code snippet achieving this:

class SurfaceFacet(AttributeFacet):
    __regid__ = 'surface'
    __select__ = AttributeFacet.__select__ & is_instance('Office')
    # this facet is a filter on the office'surface
    rtype = 'surface'
    # override the default value of operator since we want to filter
    # according to a minimal value, not an exact one
    comparator = '>='

    def vocabulary(self):
        '''override the default vocabulary method since we want to
        hard-code our threshold values.

        Not overriding would generate a filter containing all existing
        surfaces defined in the database.
        '''
        return [('> 200', '200'), ('> 250', '250'),
                ('> 275', '275'), ('> 300', '300')]
class cubicweb.web.facet.RQLPathFacet(*args, **kwargs)[source]

Base facet to filter some entities according to an arbitrary rql path. Path should be specified as a list of 3-uples or triplet string, where ‘X’ represent the filtered variable. You should specify using filter_variable the snippet variable that will be used to filter out results. You may also specify a label_variable. If you want to filter on an attribute value, you usually don’t want to specify the later since it’s the same as the filter variable, though you may have to specify the attribute type using restr_attr_type if there are some type ambiguity in the schema for the attribute.

Using this facet, we can rewrite facets we defined previously:

class AgencyFacet(RQLPathFacet):
    __regid__ = 'agency'
    # this facet should only be selected when visualizing offices
    __select__ = is_instance('Office')
    # this facet is a filter on the 'Agency' entities linked to the office
    # through the 'proposed_by' relation, where the office is the subject
    # of the relation
    path = ['X has_address O', 'O name N']
    filter_variable = 'O'
    label_variable = 'N'

class PostalCodeFacet(RQLPathFacet):
    __regid__ = 'postalcode'
    # this facet should only be selected when visualizing offices
    __select__ = is_instance('Office')
    # this facet is a filter on the PostalAddress entities linked to the
    # office through the 'has_address' relation, where the office is the
    # subject of the relation
    path = ['X has_address O', 'O postal_code PC']
    filter_variable = 'PC'

Though some features, such as ‘no value’ or automatic internationalization, won’t work. This facet class is designed to be used for cases where RelationFacet or RelationAttributeFacet can’t do the trick (e.g when you want to filter on entities where are not directly linked to the filtered entities).

class cubicweb.web.facet.RangeFacet(req, select=None, filtered_variable=None, **kwargs)[source]

This class allows to filter entities according to an attribute of numerical type.

It displays a slider using jquery to choose a lower bound and an upper bound.

The example below provides an alternative to the surface facet seen earlier, in a more powerful way since

  • lower/upper boundaries are computed according to entities to filter

  • user can specify lower/upper boundaries, not only the lower one

class SurfaceFacet(RangeFacet):
    __regid__ = 'surface'
    __select__ = RangeFacet.__select__ & is_instance('Office')
    # this facet is a filter on the office'surface
    rtype = 'surface'

All this with even less code!

The image below display the rendering of the slider:

../../../_images/facet_range.png
class cubicweb.web.facet.DateRangeFacet(req, select=None, filtered_variable=None, **kwargs)[source]

This class works similarly as the RangeFacet but for attribute of date type.

The image below display the rendering of the slider for a date range:

../../../_images/facet_date_range.png
class cubicweb.web.facet.BitFieldFacet(req, select=None, filtered_variable=None, **kwargs)[source]

Base facet class for Int field holding some bit values using binary masks.

label / value for each bit should be given using the choices attribute.

See also BitSelect.

class cubicweb.web.facet.AbstractRangeRQLPathFacet(*args, **kwargs)[source]

The AbstractRangeRQLPathFacet is the base class for RQLPathFacet-type facets allowing the use of RangeWidgets-like widgets (such as (FacetRangeWidget, class:DateFacetRangeWidget) on the parent RQLPathFacet target attribute.

class cubicweb.web.facet.RangeRQLPathFacet(*args, **kwargs)[source]

The RangeRQLPathFacet uses the FacetRangeWidget on the AbstractRangeRQLPathFacet target attribute

class cubicweb.web.facet.DateRangeRQLPathFacet(*args, **kwargs)[source]

The DateRangeRQLPathFacet uses the DateFacetRangeWidget on the AbstractRangeRQLPathFacet target attribute

Classes for facets implementor

Unless you didn’t find the class that does the job you want above, you may want to skip those classes…

class cubicweb.web.facet.AbstractFacet(req, select=None, filtered_variable=None, **kwargs)[source]

Abstract base class for all facets. Facets are stored in their own ‘facets’ registry. They are similar to contextual components since the use the following configurable properties:

  • visible, boolean flag telling if a facet should be displayed or not

  • order, integer to control facets display order

  • context, telling if a facet should be displayed in the table form filter (context = ‘tablefilter’) or in the facet box (context = ‘facetbox’) or in both (context = ‘’)

The following methods define the facet API:

get_widget()[source]

Return the widget instance to use to display this facet, or None if the facet can’t do anything valuable (only one value in the vocabulary for instance).

add_rql_restrictions()[source]

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.select) accordingly.

Facets will have the following attributes set (beside the standard AppObject ones):

  • select, the 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

Facets implementors may also be interested in the following properties / methods:

operator

Return the operator (AND or OR) to use for this facet when multiple values are selected.

rqlexec(rql, args=None)[source]

Utility method to execute some rql queries, and simply returning an empty list if Unauthorized is raised.

class cubicweb.web.facet.VocabularyFacet(req, select=None, filtered_variable=None, **kwargs)[source]

This abstract class extend AbstractFacet to use the FacetVocabularyWidget as widget, suitable for facets that may restrict values according to a (usually computed) vocabulary.

A class which inherits from VocabularyFacet must define at least these methods:

vocabulary()[source]

Return vocabulary for this facet, eg a list of 2-uple (label, value).

possible_values()[source]

Return a list of possible values (as string since it’s used to compare to a form value in javascript) for this facet.