# HG changeset patch # User David Douard # Date 1435925273 -7200 # Node ID d0907690af55bf77d8755587c7f0dc298813c7bf # Parent 36e48f7ac61a30cc1927fba76f13f09e4314fd58 [doc] move the documentations from docstring directly to the book. and improve a bit API documentations. Related to #4832808 diff -r 36e48f7ac61a -r d0907690af55 cwvreg.py --- a/cwvreg.py Fri Jul 03 15:23:14 2015 +0200 +++ b/cwvreg.py Fri Jul 03 14:07:53 2015 +0200 @@ -15,184 +15,8 @@ # # You should have received a copy of the GNU Lesser General Public License along # with CubicWeb. If not, see . -""".. RegistryStore: - -The `RegistryStore` -------------------- - -The `RegistryStore` can be seen as a two-level dictionary. It contains -all dynamically loaded objects (subclasses of :ref:`appobject`) to -build a |cubicweb| application. Basically: - -* the first level key returns a *registry*. This key corresponds to the - `__registry__` attribute of application object classes - -* the second level key returns a list of application objects which - share the same identifier. This key corresponds to the `__regid__` - attribute of application object classes. - -A *registry* holds a specific kind of application objects. There is -for instance a registry for entity classes, another for views, etc... - -The `RegistryStore` has two main responsibilities: - -- being the access point to all registries - -- handling the registration process at startup time, and during automatic - reloading in debug mode. - - -Details of the recording process -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. index:: - vregistry: registration_callback - -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 :ref:`cubelayout`. - -For each module: - -* by default all objects are registered automatically - -* if some objects have to replace other objects, or have to be - included only if some condition is met, you'll have to define a - `registration_callback(vreg)` function in your module and explicitly - register **all objects** in this module, using the api defined - below. - -.. Note:: - 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. - - -API for objects registration -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Here are the registration methods that you can use in the `registration_callback` -to register your objects to the `RegistryStore` instance given as argument (usually -named `vreg`): - -.. automethod:: cubicweb.cwvreg.CWRegistryStore.register_all - :noindex: -.. automethod:: cubicweb.cwvreg.CWRegistryStore.register_and_replace - :noindex: -.. automethod:: cubicweb.cwvreg.CWRegistryStore.register - :noindex: -.. automethod:: cubicweb.cwvreg.CWRegistryStore.unregister - :noindex: - -Examples: - -.. sourcecode:: python - - # 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. - -.. sourcecode:: python - - # 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: - -* the `SessionCleaner` class -* the `GAEAuthenticationManager` to replace the `AuthenticationManager` -* the `GAEPersistentSessionManager` to replace the `PersistentSessionManager` - -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. - - - -Runtime objects selection -~~~~~~~~~~~~~~~~~~~~~~~~~ - -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 :ref:`Concepts` section: - -* each application object has a **selector**, defined by its - `__select__` class attribute - -* this selector is responsible to return a **score** for a given context - - - 0 score means the object doesn't apply to this context - - - else, the higher the score, the better the object suits the context - -* the object with the highest score is selected. - -.. Note:: - - 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: - -* the default primary view (`__select__ = is_instance('Any')`), meaning - that the object is selectable for any kind of entity type - -* the specific `Card` primary view (`__select__ = is_instance('Card')`, - meaning that the object is selectable for Card entities - -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. - - -API for objects selections -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -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. - -.. automethod:: cubicweb.vregistry.Registry.select - :noindex: - -.. automethod:: cubicweb.vregistry.Registry.select_or_none - :noindex: - -.. automethod:: cubicweb.vregistry.Registry.possible_objects - :noindex: - -.. automethod:: cubicweb.vregistry.Registry.object_by_id - :noindex: +""" +Cubicweb registries """ __docformat__ = "restructuredtext en" @@ -234,6 +58,7 @@ sys.modules.pop('cubicweb.web.uicfg', None) sys.modules.pop('cubicweb.web.uihelper', None) + def require_appobject(obj): """return appobjects required by the given object by searching for `appobject_selectable` predicate @@ -246,11 +71,16 @@ class CWRegistry(Registry): def __init__(self, vreg): + """ + :param vreg: the :py:class:`CWRegistryStore` managing this registry. + """ super(CWRegistry, self).__init__(True) self.vreg = vreg @property def schema(self): + """The :py:class:`cubicweb.schema.CubicWebSchema` + """ return self.vreg.schema def poss_visible_objects(self, *args, **kwargs): diff -r 36e48f7ac61a -r d0907690af55 doc/api/appobject.rst --- a/doc/api/appobject.rst Fri Jul 03 15:23:14 2015 +0200 +++ b/doc/api/appobject.rst Fri Jul 03 14:07:53 2015 +0200 @@ -5,7 +5,6 @@ .. automodule:: cubicweb.appobject -.. _appobject: .. autoclass:: AppObject :show-inheritance: :members: diff -r 36e48f7ac61a -r d0907690af55 doc/api/cwvreg.rst --- a/doc/api/cwvreg.rst Fri Jul 03 15:23:14 2015 +0200 +++ b/doc/api/cwvreg.rst Fri Jul 03 14:07:53 2015 +0200 @@ -5,9 +5,14 @@ .. automodule:: cubicweb.cwvreg + .. autoclass:: CWRegistryStore + :show-inheritance: + :members: + :undoc-members: + .. autoclass:: CWRegistry :show-inheritance: - :members: + :members: schema, poss_visible_objects, select .. autoclass:: InstancesRegistry :show-inheritance: @@ -33,6 +38,8 @@ :show-inheritance: :members: - .. autoclass:: CWRegistryStore - :show-inheritance: - :members: + +:mod:`logilab.common.registry` +============================== + +.. automodule:: logilab.common.registry diff -r 36e48f7ac61a -r d0907690af55 doc/api/predicates.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doc/api/predicates.rst Fri Jul 03 14:07:53 2015 +0200 @@ -0,0 +1,57 @@ +.. _predicates_module: + +:mod:`cubicweb.predicates` +========================== + +.. automodule:: cubicweb.predicates + + .. autoclass:: cubicweb.appobject.yes + .. autoclass:: cubicweb.predicates.match_kwargs + .. autoclass:: cubicweb.predicates.appobject_selectable + .. autoclass:: cubicweb.predicates.adaptable + .. autoclass:: cubicweb.predicates.configuration_values + + .. autoclass:: cubicweb.predicates.none_rset + .. autoclass:: cubicweb.predicates.any_rset + .. autoclass:: cubicweb.predicates.nonempty_rset + .. autoclass:: cubicweb.predicates.empty_rset + .. autoclass:: cubicweb.predicates.one_line_rset + .. autoclass:: cubicweb.predicates.multi_lines_rset + .. autoclass:: cubicweb.predicates.multi_columns_rset + .. autoclass:: cubicweb.predicates.paginated_rset + .. autoclass:: cubicweb.predicates.sorted_rset + .. autoclass:: cubicweb.predicates.one_etype_rset + .. autoclass:: cubicweb.predicates.multi_etypes_rset + + .. autoclass:: cubicweb.predicates.non_final_entity + .. autoclass:: cubicweb.predicates.is_instance + .. autoclass:: cubicweb.predicates.score_entity + .. autoclass:: cubicweb.predicates.rql_condition + .. autoclass:: cubicweb.predicates.relation_possible + .. autoclass:: cubicweb.predicates.partial_relation_possible + .. autoclass:: cubicweb.predicates.has_related_entities + .. autoclass:: cubicweb.predicates.partial_has_related_entities + .. autoclass:: cubicweb.predicates.has_permission + .. autoclass:: cubicweb.predicates.has_add_permission + .. autoclass:: cubicweb.predicates.has_mimetype + .. autoclass:: cubicweb.predicates.is_in_state + .. autofunction:: cubicweb.predicates.on_fire_transition + + .. autoclass:: cubicweb.predicates.match_user_groups + + .. autoclass:: cubicweb.predicates.no_cnx + .. autoclass:: cubicweb.predicates.anonymous_user + .. autoclass:: cubicweb.predicates.authenticated_user + .. autoclass:: cubicweb.predicates.match_form_params + .. autoclass:: cubicweb.predicates.match_search_state + .. autoclass:: cubicweb.predicates.match_context_prop + .. autoclass:: cubicweb.predicates.match_context + .. autoclass:: cubicweb.predicates.match_view + .. autoclass:: cubicweb.predicates.primary_view + .. autoclass:: cubicweb.predicates.contextual + .. autoclass:: cubicweb.predicates.specified_etype_implements + .. autoclass:: cubicweb.predicates.attribute_edited + .. autoclass:: cubicweb.predicates.match_transition + + .. autoclass:: cubicweb.predicates.match_exception + .. autoclass:: cubicweb.predicates.debug_mode diff -r 36e48f7ac61a -r d0907690af55 doc/book/devrepo/vreg.rst --- a/doc/book/devrepo/vreg.rst Fri Jul 03 15:23:14 2015 +0200 +++ b/doc/book/devrepo/vreg.rst Fri Jul 03 14:07:53 2015 +0200 @@ -12,15 +12,191 @@ An overview of AppObjects, the VRegistry and Selectors is given in the :ref:`VRegistryIntro` chapter. -.. autodocstring:: cubicweb.cwvreg - :noindex: -.. autodocstring:: cubicweb.predicates - :noindex: -.. automodule:: cubicweb.appobject - :noindex: + + +The :class:`CWRegistryStore` +---------------------------- + +The :class:`CWRegistryStore ` can be +seen as a two-level dictionary. It contains all dynamically loaded +objects (subclasses of :class:`AppObject `) +to build a |cubicweb| application. Basically: + +* the first level key returns a *registry*. This key corresponds to the + `__registry__` attribute of application object classes + +* the second level key returns a list of application objects which + share the same identifier. This key corresponds to the `__regid__` + attribute of application object classes. + +A *registry* holds a specific kind of application objects. There is +for instance a registry for entity classes, another for views, etc... + +The :class:`CWRegistryStore ` has two +main responsibilities: + +- being the access point to all registries + +- handling the registration process at startup time, and during automatic + reloading in debug mode. + + +Details of the recording process +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. index:: + vregistry: registration_callback + +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 :ref:`cubelayout`. + +For each module: + +* by default all objects are registered automatically + +* if some objects have to replace other objects, or have to be + included only if some condition is met, you'll have to define a + `registration_callback(vreg)` function in your module and explicitly + register **all objects** in this module, using the api defined + below. + +.. Note:: + 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. + + +API for objects registration +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Here are the registration methods that you can use in the +`registration_callback` to register your objects to the +:class:`CWRegistryStore` instance given as argument (usually named +`vreg`): + +- :py:meth:`register_all() ` +- :py:meth:`register_and_replace() ` +- :py:meth:`register() ` +- :py:meth:`unregister() ` + +Examples: + +.. sourcecode:: python + + # 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. + +.. sourcecode:: python -Base predicates ---------------- + # 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: + +* the `SessionCleaner` class +* the `GAEAuthenticationManager` to replace the `AuthenticationManager` +* the `GAEPersistentSessionManager` to replace the `PersistentSessionManager` + +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. + + + +Runtime objects selection +~~~~~~~~~~~~~~~~~~~~~~~~~ + +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 :ref:`Concepts` section: + +* each application object has a **selector**, defined by its + `__select__` class attribute + +* this selector is responsible to return a **score** for a given context + + - 0 score means the object doesn't apply to this context + + - else, the higher the score, the better the object suits the context + +* the object with the highest score is selected. + +.. Note:: + + 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: + +* the default primary view (`__select__ = is_instance('Any')`), meaning + that the object is selectable for any kind of entity type + +* the specific `Card` primary view (`__select__ = is_instance('Card')`, + meaning that the object is selectable for Card entities + +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. + + +API for objects selections +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +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. + +:py:meth:`select() ` + +:py:meth:`select_or_none() ` + +:py:meth:`possible_objects() ` + +:py:meth:`object_by_id() ` + + +The `AppObject` class +--------------------- + +The :py:class:`cubicweb.appobject.AppObject` class is the base class +for all dynamically loaded objects (application objects) accessible +through the :py:class:`cubicweb.cwvreg.CWRegistryStore`. + + +Predicates and selectors +------------------------ 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 @@ -31,6 +207,173 @@ Of course you may have to write your own set of predicates as your needs grows and you get familiar with the framework (see :ref:`CustomPredicates`). +A predicate is a class testing a particular aspect of a context. A selector is +built by combining existant predicates or even selectors. + +Using and combining existant predicates +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +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. + +Example +~~~~~~~ + +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 +:class:`~cubicweb.entities.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: + +.. sourcecode:: python + + class RSSIconBox(box.Box): + ''' just display the RSS icon on uniform result set ''' + __select__ = box.Box.__select__ & non_final_entity() + +It takes into account: + +* the inherited selection criteria (one has to look them up in the class + hierarchy to know the details) + +* :class:`~cubicweb.predicates.non_final_entity`, which filters on result sets + containing non final entities (a 'final entity' being synonym for entity + attributes type, eg `String`, `Int`, etc) + +This matches our second case. Hence we have to provide a specific component for +the first case: + +.. sourcecode:: python + + 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 :class:`~cubicweb.predicates.one_line_rset` predicate, which +filters result sets of size 1. Thus, on a result set containing multiple +entities, :class:`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: + +* fill in the call method of `EntityRSSIconBox` + +* provide the default implementation of the method returning the RSS stream url + on :class:`~cubicweb.entities.AnyEntity` + +* redefine this method on `Blog`. + + +When to use selectors? +~~~~~~~~~~~~~~~~~~~~~~ + +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: + +.. sourcecode:: python + + 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. + +.. sourcecode:: python + + 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. + + +.. _CustomPredicates: + +Defining your own predicates +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can use the :py:func:`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: + +- :py:class:`ExpectedValuePredicate ` +- :py:class:`EClassPredicate ` +- :py:class:`EntityPredicate ` + + +.. _DebuggingSelectors: + +Debugging selection +~~~~~~~~~~~~~~~~~~~ + +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 +:class:`traced_selection ` +context manager to help with that, *if you're running your instance in +debug mode*. + + +Base predicates +--------------- + Here is a description of generic predicates provided by CubicWeb that should suit most of your needs. @@ -39,16 +382,11 @@ Those predicates are somewhat dumb, which doesn't mean they're not (very) useful. -.. autoclass:: cubicweb.appobject.yes - :noindex: -.. autoclass:: cubicweb.predicates.match_kwargs - :noindex: -.. autoclass:: cubicweb.predicates.appobject_selectable - :noindex: -.. autoclass:: cubicweb.predicates.adaptable - :noindex: -.. autoclass:: cubicweb.predicates.configuration_values - :noindex: +- :py:class:`yes ` +- :py:class:`match_kwargs ` +- :py:class:`appobject_selectable ` +- :py:class:`adaptable ` +- :py:class:`configuration_values ` Result set predicates @@ -59,28 +397,17 @@ 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. -.. autoclass:: cubicweb.predicates.none_rset - :noindex: -.. autoclass:: cubicweb.predicates.any_rset - :noindex: -.. autoclass:: cubicweb.predicates.nonempty_rset - :noindex: -.. autoclass:: cubicweb.predicates.empty_rset - :noindex: -.. autoclass:: cubicweb.predicates.one_line_rset - :noindex: -.. autoclass:: cubicweb.predicates.multi_lines_rset - :noindex: -.. autoclass:: cubicweb.predicates.multi_columns_rset - :noindex: -.. autoclass:: cubicweb.predicates.paginated_rset - :noindex: -.. autoclass:: cubicweb.predicates.sorted_rset - :noindex: -.. autoclass:: cubicweb.predicates.one_etype_rset - :noindex: -.. autoclass:: cubicweb.predicates.multi_etypes_rset - :noindex: +- :py:class:`none_rset ` +- :py:class:`any_rset ` +- :py:class:`nonempty_rset ` +- :py:class:`empty_rset ` +- :py:class:`one_line_rset ` +- :py:class:`multi_lines_rset ` +- :py:class:`multi_columns_rset ` +- :py:class:`paginated_rset ` +- :py:class:`sorted_rset ` +- :py:class:`one_etype_rset ` +- :py:class:`multi_etypes_rset ` Entity predicates @@ -90,32 +417,19 @@ 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. -.. autoclass:: cubicweb.predicates.non_final_entity - :noindex: -.. autoclass:: cubicweb.predicates.is_instance - :noindex: -.. autoclass:: cubicweb.predicates.score_entity - :noindex: -.. autoclass:: cubicweb.predicates.rql_condition - :noindex: -.. autoclass:: cubicweb.predicates.relation_possible - :noindex: -.. autoclass:: cubicweb.predicates.partial_relation_possible - :noindex: -.. autoclass:: cubicweb.predicates.has_related_entities - :noindex: -.. autoclass:: cubicweb.predicates.partial_has_related_entities - :noindex: -.. autoclass:: cubicweb.predicates.has_permission - :noindex: -.. autoclass:: cubicweb.predicates.has_add_permission - :noindex: -.. autoclass:: cubicweb.predicates.has_mimetype - :noindex: -.. autoclass:: cubicweb.predicates.is_in_state - :noindex: -.. autofunction:: cubicweb.predicates.on_fire_transition - :noindex: +- :py:class:`non_final_entity ` +- :py:class:`is_instance ` +- :py:class:`score_entity ` +- :py:class:`rql_condition ` +- :py:class:`relation_possible ` +- :py:class:`partial_relation_possible ` +- :py:class:`has_related_entities ` +- :py:class:`partial_has_related_entities ` +- :py:class:`has_permission ` +- :py:class:`has_add_permission ` +- :py:class:`has_mimetype ` +- :py:class:`is_in_state ` +- :py:func:`on_fire_transition ` Logged user predicates @@ -123,8 +437,7 @@ Those predicates are looking for properties of the user issuing the request. -.. autoclass:: cubicweb.predicates.match_user_groups - :noindex: +- :py:class:`match_user_groups ` Web request predicates @@ -133,40 +446,26 @@ Those predicates are looking for properties of *web* request, they can not be used on the data repository side. -.. autoclass:: cubicweb.predicates.no_cnx - :noindex: -.. autoclass:: cubicweb.predicates.anonymous_user - :noindex: -.. autoclass:: cubicweb.predicates.authenticated_user - :noindex: -.. autoclass:: cubicweb.predicates.match_form_params - :noindex: -.. autoclass:: cubicweb.predicates.match_search_state - :noindex: -.. autoclass:: cubicweb.predicates.match_context_prop - :noindex: -.. autoclass:: cubicweb.predicates.match_context - :noindex: -.. autoclass:: cubicweb.predicates.match_view - :noindex: -.. autoclass:: cubicweb.predicates.primary_view - :noindex: -.. autoclass:: cubicweb.predicates.contextual - :noindex: -.. autoclass:: cubicweb.predicates.specified_etype_implements - :noindex: -.. autoclass:: cubicweb.predicates.attribute_edited - :noindex: -.. autoclass:: cubicweb.predicates.match_transition - :noindex: +- :py:class:`no_cnx ` +- :py:class:`anonymous_user ` +- :py:class:`authenticated_user ` +- :py:class:`match_form_params ` +- :py:class:`match_search_state ` +- :py:class:`match_context_prop ` +- :py:class:`match_context ` +- :py:class:`match_view ` +- :py:class:`primary_view ` +- :py:class:`contextual ` +- :py:class:`specified_etype_implements ` +- :py:class:`attribute_edited ` +- :py:class:`match_transition ` Other predicates ~~~~~~~~~~~~~~~~ -.. autoclass:: cubicweb.predicates.match_exception - :noindex: -.. autoclass:: cubicweb.predicates.debug_mode - :noindex: + +- :py:class:`match_exception ` +- :py:class:`debug_mode ` You'll also find some other (very) specific predicates hidden in other modules than :mod:`cubicweb.predicates`. diff -r 36e48f7ac61a -r d0907690af55 predicates.py --- a/predicates.py Fri Jul 03 15:23:14 2015 +0200 +++ b/predicates.py Fri Jul 03 14:07:53 2015 +0200 @@ -15,171 +15,7 @@ # # You should have received a copy of the GNU Lesser General Public License along # with CubicWeb. If not, see . -""".. _Selectors: - -Predicates and selectors ------------------------- - -A predicate is a class testing a particular aspect of a context. A selector is -built by combining existant predicates or even selectors. - -Using and combining existant predicates -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -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. - -Example -~~~~~~~ - -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 -:class:`~cubicweb.entities.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: - -.. sourcecode:: python - - class RSSIconBox(box.Box): - ''' just display the RSS icon on uniform result set ''' - __select__ = box.Box.__select__ & non_final_entity() - -It takes into account: - -* the inherited selection criteria (one has to look them up in the class - hierarchy to know the details) - -* :class:`~cubicweb.predicates.non_final_entity`, which filters on result sets - containing non final entities (a 'final entity' being synonym for entity - attributes type, eg `String`, `Int`, etc) - -This matches our second case. Hence we have to provide a specific component for -the first case: - -.. sourcecode:: python - - 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 :class:`~cubicweb.predicates.one_line_rset` predicate, which -filters result sets of size 1. Thus, on a result set containing multiple -entities, :class:`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: - -* fill in the call method of `EntityRSSIconBox` - -* provide the default implementation of the method returning the RSS stream url - on :class:`~cubicweb.entities.AnyEntity` - -* redefine this method on `Blog`. - - -When to use selectors? -~~~~~~~~~~~~~~~~~~~~~~ - -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: - -.. sourcecode:: python - - 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. - -.. sourcecode:: python - - 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. - - -.. _CustomPredicates: - -Defining your own predicates -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. autodocstring:: cubicweb.appobject::objectify_predicate - -In other cases, you can take a look at the following abstract base classes: - -.. autoclass:: cubicweb.predicates.ExpectedValuePredicate -.. autoclass:: cubicweb.predicates.EClassPredicate -.. autoclass:: cubicweb.predicates.EntityPredicate - -.. _DebuggingSelectors: - -Debugging selection -~~~~~~~~~~~~~~~~~~~ - -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 :class:`logilab.common.registry.traced_selection` context -manager to help with that, *if you're running your instance in debug mode*. - -.. autoclass:: logilab.common.registry.traced_selection - +"""Predicate classes """ __docformat__ = "restructuredtext en"