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


  • Building my photos web site with CubicWeb (Part I)

    2010/04/01 by Sylvain Thenault

    Desired features

    • photo gallery;
    • photo stored onto the fs and displayed through a web interface dynamically;
    • navigation through folder (album), tags, geographical zone, people on the picture... using facets;
    • advanced security (eg not everyone can see everything). More on this later.

    Let's go then

    Step 1: creating a new cube for my web site

    One note about my development environment: I wanted to use packaged version of CubicWeb and cubes while keeping my cube in my user directory, let's say ~src/cubes. It can be done by setting the following environment variables:

    CW_CUBES_PATH=~/src/cubes
    CW_MODE=user
    

    The new cube, holding custom code for this web site, can now be created using:

    cubicweb-ctl newcube --directory=~/src/cubes sytweb
    

    Step 2: pick building blocks into existing cubes

    Almost everything I want to represent in my web-site is somewhat already modelized in existing cubes that I'll extend for my needs:

    • folder, containing Folder entity type, which will be used as both 'album' and a way to map file system folders. Entities are added to a given folder using the filed_under relation.
    • file, containing File and Image entity type, gallery view, and a file system import utility.
    • zone, containing the Zone entity type for hierarchical geographical zones. Entities (including sub-zones) are added to a given zone using the situated_in relation.
    • person, containing the Person entity type plus some basic views.
    • comment, providing a full commenting system allowing one to comment entity types supporting the comments relation by adding a Comment entity.
    • tag, providing a full tagging system as an easy and powerful way to classify entities supporting the tags relation by linking the to Tag entities. This will allow navigation into a large number of pictures.

    Ok, now I'll tell my cube requires all this by editing cubes/sytweb/__pkginfo__.py:

    __depends_cubes__ = {'file': '>= 1.2.0',
                         'folder': '>= 1.1.0',
                         'person': '>= 1.2.0',
                         'comment': '>= 1.2.0',
                         'tag': '>= 1.2.0',
                         'zone': None,
                         }
    __depends__ = {'cubicweb': '>= 3.5.10',
                   }
    for key,value in __depends_cubes__.items():
        __depends__['cubicweb-'+key] = value
    __use__ = tuple(__depends_cubes__)
    

    Notice that you can express minimal version of the cube that should be used, None meaning whatever version available.

    Step 3: glue everything together in my cube's schema

    from yams.buildobjs import RelationDefinition
    
    class comments(RelationDefinition):
        subject = 'Comment'
        object = ('File', 'Image')
        cardinality = '1*'
        composite = 'object'
    
    class tags(RelationDefinition):
        subject = 'Tag'
        object = ('File', 'Image')
    
    class filed_under(RelationDefinition):
        subject = ('File', 'Image')
        object = 'Folder'
    
    class situated_in(RelationDefinition):
        subject = 'Image'
        object = 'Zone'
    
    class displayed_on(RelationDefinition):
        subject = 'Person'
        object = 'Image'
    

    This schema:

    • allows to comment and tag File and Image entity types by adding the comments and tags relations. This should be all we have to do for this feature since the related cubes provide 'pluggable section' which are automatically displayed in the primary view of entity types supporting the relation.
    • adds a situated_in relation definition so that image entities can be geolocalized.
    • add a new relation displayed_on relation telling who can be seen on a picture.

    This schema will probably have to evolve as time goes (for security handling at least), but since the possibility to change and update the schema evolving is one of CubicWeb features (and goals), we won't worry and see that later when needed.

    Step 4: creating the instance

    Now that I have a schema, I want to create an instance of that new 'sytweb' cube, so I run:

    cubicweb-ctl create sytweb sytweb_instance
    

    hint: if you get an error while the database is initialized, you can avoid having to reanswer to questions by running

    cubicweb-ctl db-create sytweb_instance
    

    This will use your already configured instance and start directly from the database creation step, thus skipping questions asked by the 'create' command.

    Once the instance and database are fully initialized, run

    cubicweb-ctl start sytweb_instance
    

    to start the instance, check you can connect on it, etc...

    Next times

    We will customize the index page, see security configuration, use the Bytes FileSystem Storage... Lots of cool stuff remaining :)

    Next post : security, testing and migration