Using Facets in Cubicweb

Recently, for internal purposes, we've made a little cubicweb application to help us organizing visits to find new office locations. Here's an excerpt of the schema:

class Office(WorkflowableEntityType):
price = Int(description='euros / m2 / HC / HT')
surface = Int(description='m2')
description = RichString(fulltextindexed=True)
has_address = SubjectRelation('PostalAddress', cardinality='1?', composite='subject')
proposed_by = SubjectRelation('Agency')
comments = ObjectRelation('Comment', cardinality='1*', composite='object')
screenshots = SubjectRelation(('File', 'Image'), cardinality='*1',

The two other entity types defined in the schema are Visit and Agency but we can also guess from the above that this application uses the two cubes comment and addressbook (remember, cubicweb is only a game where you assemble cubes !).

While we know that just defining the schema in enough to have a full, usable, (testable !) application, we also know that every application needs to be customized to fulfill the needs it was built for. So in this case, what we needed most was some custom filters that would let us restrict searches according to surfaces, prices or zipcodes. Fortunately for us, Cubicweb provides the facets (image) mechanism and a few base classes that make the task quite easy:

class PostalCodeFacet(RelationFacet):
id = 'postalcode-facet'             # every registered class must have an id
select = implements('Office')   # this facet should only be selected when
# visualizing offices
rtype = 'has_address'               # this facet is a filter on the entity linked to
# the office thrhough the relation has_address
target_attr = 'postalcode'          # the filter's key is the attribute "postal_code"
# of the target PostalAddress entity

This is a typical RelationFacet: we want to be able to filter offices according to the attribute postalcode of their associated PostalAdress. Each line in the class is explained by the comment on its right.

Now, here is the code to define a filter based on the surface attribute of the Office:

class SurfaceFacet(AttributeFacet):
id = 'surface-facet'              # every registered class must have an id
select = implements('Office') # this facet should only be selected when
# visualizing offices
rtype = 'surface'                 # the filter's key is the attribute "surface"
comparator = '>='                 # override the default value of operator since
# we want to filter according to a minimal
# value, not an exact onedef rset_vocabulary(self, ___):
"""override the default vocabulary method since we want to hard-code
our threshold values.
Not overriding would generate a filter box with all existing surfaces
defined in the database.
return [('> 200', '200'), ('> 250', '250'),
('> 275', '275'), ('> 300', '300')]

And that's it: we have two filter boxes automatically displayed on each page presenting more than one office. The price facet is basically the same as the surface one but with a different vocabulary and with rtype = 'price'.

(The cube also benefits from the builtin google map views defined by cubicweb but that's for another blog).