This chapter deals with some of the core concepts of the CubicWeb framework which make it different from other frameworks (and maybe not easy to grasp at a first glance). To be able to do advanced development with CubicWeb you need a good understanding of what is explained below.
This chapter goes deep into details. You don’t have to remember them all but keep it in mind so you can go back there later.
An overview of AppObjects, the VRegistry and Selectors is given in the Registries and application objects chapter.
A registry holds a specific kind of application objects. There is for instance a registry for entity classes, another for views, etc...
The CWRegistryStore has two main responsibilities:
On startup, CubicWeb loads application objects defined in its library and in cubes used by the instance. Application objects from the library are loaded first, then those provided by cubes are loaded in dependency order (e.g. if your cube depends on an other, objects from the dependency will be loaded first). The layout of the modules or packages in a cube is explained in Standard structure for a cube.
For each module:
Once the function registration_callback(vreg) is implemented in a module, all the objects from this module have to be explicitly registered as it disables the automatic objects registration.
Here are the registration methods that you can use in the registration_callback to register your objects to the CWRegistryStore instance given as argument (usually named vreg):
# web/views/basecomponents.py def registration_callback(vreg): # register everything in the module except SeeAlsoComponent vreg.register_all(globals().itervalues(), __name__, (SeeAlsoVComponent,)) # conditionally register SeeAlsoVComponent if 'see_also' in vreg.schema: vreg.register(SeeAlsoVComponent)
In this example, we register all application object classes defined in the module except SeeAlsoVComponent. This class is then registered only if the ‘see_also’ relation type is defined in the instance’schema.
# goa/appobjects/sessions.py def registration_callback(vreg): vreg.register(SessionsCleaner) # replace AuthenticationManager by GAEAuthenticationManager vreg.register_and_replace(GAEAuthenticationManager, AuthenticationManager) # replace PersistentSessionManager by GAEPersistentSessionManager vreg.register_and_replace(GAEPersistentSessionManager, PersistentSessionManager)
In this example, we explicitly register classes one by one:
If at some point we register a new appobject class in this module, it won’t be registered at all without modification to the registration_callback implementation. The previous example will register it though, thanks to the call to the register_all method.
Now that we have all application objects loaded, the question is : when I want some specific object, for instance the primary view for a given entity, how do I get the proper object ? This is what we call the selection mechanism.
As explained in the The Core Concepts of CubicWeb section:
When no single object has the highest score, an exception is raised in development mode to let you know that the engine was not able to identify the view to apply. This error is silenced in production mode and one of the objects with the highest score is picked.
In such cases you would need to review your design and make sure your selectors or appobjects are properly defined. Such an error is typically caused by either forgetting to change the __regid__ in a derived class, or by having copy-pasted some code.
For instance, if you are selecting the primary (__regid__ = ‘primary’) view (__registry__ = ‘views’) for a result set containing a Card entity, two objects will probably be selectable:
Other primary views specific to other entity types won’t be selectable in this case. Among selectable objects, the is_instance(‘Card’) selector will return a higher score since it’s more specific, so the correct view will be selected as expected.
Here is the selection API you’ll get on every registry. Some of them, as the ‘etypes’ registry, containing entity classes, extend it. In those methods, *args, **kwargs is what we call the context. Those arguments are given to selectors that will inspect their content and return a score accordingly.
Predicates are scoring functions that are called by the registry to tell whenever an appobject can be selected in a given context. Predicates may be chained together using operators to build a selector. A selector is the glue that tie views to the data model or whatever input context. Using them appropriately is an essential part of the construction of well behaved cubes.
Of course you may have to write your own set of predicates as your needs grows and you get familiar with the framework (see Defining your own predicates).
A predicate is a class testing a particular aspect of a context. A selector is built by combining existant predicates or even selectors.
You can combine predicates using the &, | and ~ operators.
When two predicates are combined using the & operator, it means that both should return a positive score. On success, the sum of scores is returned.
When two predicates are combined using the | operator, it means that one of them should return a positive score. On success, the first positive score is returned.
You can also “negate” a predicate by precedeing it by the ~ unary operator.
Of course you can use parenthesis to balance expressions.
The goal: when on a blog, one wants the RSS link to refer to blog entries, not to the blog entity itself.
To do that, one defines a method on entity classes that returns the RSS stream url for a given entity. The default implementation on AnyEntity (the generic entity class used as base for all others) and a specific implementation on Blog will do what we want.
But when we have a result set containing several Blog entities (or different entities), we don’t know on which entity to call the aforementioned method. In this case, we keep the generic behaviour.
Hence we have two cases here, one for a single-entity rsets, the other for multi-entities rsets.
In web/views/boxes.py lies the RSSIconBox class. Look at its selector:
class RSSIconBox(box.Box): ''' just display the RSS icon on uniform result set ''' __select__ = box.Box.__select__ & non_final_entity()
It takes into account:
This matches our second case. Hence we have to provide a specific component for the first case:
class EntityRSSIconBox(RSSIconBox): '''just display the RSS icon on uniform result set for a single entity''' __select__ = RSSIconBox.__select__ & one_line_rset()
Here, one adds the one_line_rset predicate, which filters result sets of size 1. Thus, on a result set containing multiple entities, one_line_rset makes the EntityRSSIconBox class non selectable. However for a result set with one entity, the EntityRSSIconBox class will have a higher score than RSSIconBox, which is what we wanted.
Of course, once this is done, you have to:
Selectors are to be used whenever arises the need of dispatching on the shape or content of a result set or whatever else context (value in request form params, authenticated user groups, etc...). That is, almost all the time.
Here is a quick example:
class UserLink(component.Component): '''if the user is the anonymous user, build a link to login else a link to the connected user object with a logout link ''' __regid__ = 'loggeduserlink' def call(self): if self._cw.session.anonymous_session: # display login link ... else: # display a link to the connected user object with a loggout link ...
The proper way to implement this with CubicWeb is two have two different classes sharing the same identifier but with different selectors so you’ll get the correct one according to the context.
class UserLink(component.Component): '''display a link to the connected user object with a loggout link''' __regid__ = 'loggeduserlink' __select__ = component.Component.__select__ & authenticated_user() def call(self): # display useractions and siteactions ... class AnonUserLink(component.Component): '''build a link to login''' __regid__ = 'loggeduserlink' __select__ = component.Component.__select__ & anonymous_user() def call(self): # display login link ...
The big advantage, aside readability once you’re familiar with the system, is that your cube becomes much more easily customizable by improving componentization.
You can use the objectify_predicate decorator to easily write your own predicates as simple python functions.
In other cases, you can take a look at the following abstract base classes:
Once in a while, one needs to understand why a view (or any application object) is, or is not selected appropriately. Looking at which predicates fired (or did not) is the way. The traced_selection context manager to help with that, if you’re running your instance in debug mode.
Here is a description of generic predicates provided by CubicWeb that should suit most of your needs.
Those predicates are somewhat dumb, which doesn’t mean they’re not (very) useful.
Those predicates are looking for a result set in the context (‘rset’ argument or the input context) and match or not according to its shape. Some of these predicates have different behaviour if a particular cell of the result set is specified using ‘row’ and ‘col’ arguments of the input context or not.
Those predicates are looking for either an entity argument in the input context, or entity found in the result set (‘rset’ argument or the input context) and match or not according to entity’s (instance or class) properties.
Those predicates are looking for properties of the user issuing the request.
Those predicates are looking for properties of web request, they can not be used on the data repository side.