Blog entries

  • Using Facets in Cubicweb

    2009/02/25 by Adrien Di Mascio

    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',
                                      composite='subject')
    

    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 one
    
        def 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).


  • Google Maps and CubicWeb

    2009/03/09 by Adrien Di Mascio
    http://maps.google.com/intl/fr_ALL/images/maps_logo_small_blue.png

    There is this so-called 'gmap-view' in CubicWeb, the question is: how to use it ?

    Well, first, no surprise, you have to generate an API key to be able to use google maps on your server (make sure your usage conforms the terms as defined by Google).

    Now, let's say you have defined the following schema:

    class Company(EntityType):
        name = String(required=True, maxsize=64)
        # ... some other attributes ...
        latitude = Float(required=True)
        longitude = Float(required=True)
    
    class Employee(EntityType):
        # ... some attributes ...
        works_for = SubjectRelation('Company', cardinality='1*')
    

    And you'd like to be able to display companies on a map; you've also got these nice icons that you'd wish to use as markers on the map. First thing, define those three icons as external resources. You can do that by editing your CUBE/data/external_resources file:

    SMALL_MARKER_ICON=DATADIR/small_company.png
    MEDIUM_MARKER_ICON=DATADIR/MEDIUM_company.png
    BIG_MARKER_ICON=DATADIR/big_company.png
    

    We're nearly done, now. We just have to make our entity class implement the cubicweb.interfaces.IGeocodable interface. Here's an example:

    from cubicweb.entities import AnyEntity
    from cubicweb.interfaces import IGeocodable
    
    class Company(AnyEntity):
        id = 'Company' # this must match the type as defined in your schema
        __implements__ = AnyEntity.__implements__ + (IGeocodable,)
    
        def size(self):
            return self.req.execute('Any COUNT(E) WHERE E works_for C, C eid %(c)s',
                                    {'c': self.eid})
    
        # this is a method of IGeocodable
        def marker_icon(self):
            size = self.size()
            if size < 20:
                return self.req_external_resource('SMALL_MARKER_ICON')
            elif size < 500:
                return self.req_external_resource('MEDIUM_MARKER_ICON')
            else:
                return self.req_external_resource('BIG_MARKER_ICON')
    

    That's it, you can now call the gmap-view on a resultset containing companies:

    rset = self.req.execute('Any C WHERE C is Company')
    self.wview(rset, 'gmap-view', gmap_key=YOUR_API_KEY)
    

    Further configuration is possible, especially to control the size of the map or the default zoom level.

    To be fair, I must say that in a real-life cube, chances are you won't be able to specificy directly latitude and longitude and that you'll only have an address. This is slightly more complex to do since you'll need to query a geocoding service (the google one for instance) to transform your address into latitude/longitude. This will typically be done in a hook

    Here is an screenshot of google maps on a production site, the museums in Normandy :

    http://www.cubicweb.org/file/229641?vid=download

  • Some new standard facets on the way

    2009/05/29 by Adrien Di Mascio

    CubicWeb has this really nice builtin facet system to define restrictions filters really as easily as possible.

    We've just added two new kind of facets in CubicWeb :

    • The RangeFacet which displays a slider using jquery to choose a lower bound and an upper bound. The RangeWidget works with either numerical values or date values
    • The HasRelationFacet which displays a simple checkbox and lets you refine your selection in order to get only entities that actually use this relation.
    http://www.cubicweb.org/file/343498?vid=download

    Here's an example of code that defines a facet to filter musical works according to their composition date:

    class CompositionDateFacet(DateRangeFacet):
        # 1. make sure this facet is displayed only on Track selection
        __select__ = DateRangeFacet.__select__ & implements('Track')
        # 2. give the facet an id (required by CubicWeb)
        id = 'compdate-facet'
        # 3. specify the attribute name that actually stores the date in the DB
        rtype = 'composition_date'
    

    And that's it, on each page displaying tracks, you'll be able to filter them according to their composition date with a jquery slider.

    All this, brought by CubicWeb (in the next 3.3 version)