predicates.py
changeset 10499 d0907690af55
parent 10498 36e48f7ac61a
child 10599 99e9fe1e633f
child 10646 45671fb330f5
equal deleted inserted replaced
10498:36e48f7ac61a 10499:d0907690af55
    13 # FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
    13 # FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
    14 # details.
    14 # details.
    15 #
    15 #
    16 # You should have received a copy of the GNU Lesser General Public License along
    16 # You should have received a copy of the GNU Lesser General Public License along
    17 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
    17 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
    18 """.. _Selectors:
    18 """Predicate classes
    19 
       
    20 Predicates and selectors
       
    21 ------------------------
       
    22 
       
    23 A predicate is a class testing a particular aspect of a context. A selector is
       
    24 built by combining existant predicates or even selectors.
       
    25 
       
    26 Using and combining existant predicates
       
    27 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
       
    28 
       
    29 You can combine predicates using the `&`, `|` and `~` operators.
       
    30 
       
    31 When two predicates are combined using the `&` operator, it means that
       
    32 both should return a positive score. On success, the sum of scores is
       
    33 returned.
       
    34 
       
    35 When two predicates are combined using the `|` operator, it means that
       
    36 one of them should return a positive score. On success, the first
       
    37 positive score is returned.
       
    38 
       
    39 You can also "negate" a predicate by precedeing it by the `~` unary operator.
       
    40 
       
    41 Of course you can use parenthesis to balance expressions.
       
    42 
       
    43 Example
       
    44 ~~~~~~~
       
    45 
       
    46 The goal: when on a blog, one wants the RSS link to refer to blog entries, not to
       
    47 the blog entity itself.
       
    48 
       
    49 To do that, one defines a method on entity classes that returns the
       
    50 RSS stream url for a given entity. The default implementation on
       
    51 :class:`~cubicweb.entities.AnyEntity` (the generic entity class used
       
    52 as base for all others) and a specific implementation on `Blog` will
       
    53 do what we want.
       
    54 
       
    55 But when we have a result set containing several `Blog` entities (or
       
    56 different entities), we don't know on which entity to call the
       
    57 aforementioned method. In this case, we keep the generic behaviour.
       
    58 
       
    59 Hence we have two cases here, one for a single-entity rsets, the other for
       
    60 multi-entities rsets.
       
    61 
       
    62 In web/views/boxes.py lies the RSSIconBox class. Look at its selector:
       
    63 
       
    64 .. sourcecode:: python
       
    65 
       
    66   class RSSIconBox(box.Box):
       
    67     ''' just display the RSS icon on uniform result set '''
       
    68     __select__ = box.Box.__select__ & non_final_entity()
       
    69 
       
    70 It takes into account:
       
    71 
       
    72 * the inherited selection criteria (one has to look them up in the class
       
    73   hierarchy to know the details)
       
    74 
       
    75 * :class:`~cubicweb.predicates.non_final_entity`, which filters on result sets
       
    76   containing non final entities (a 'final entity' being synonym for entity
       
    77   attributes type, eg `String`, `Int`, etc)
       
    78 
       
    79 This matches our second case. Hence we have to provide a specific component for
       
    80 the first case:
       
    81 
       
    82 .. sourcecode:: python
       
    83 
       
    84   class EntityRSSIconBox(RSSIconBox):
       
    85     '''just display the RSS icon on uniform result set for a single entity'''
       
    86     __select__ = RSSIconBox.__select__ & one_line_rset()
       
    87 
       
    88 Here, one adds the :class:`~cubicweb.predicates.one_line_rset` predicate, which
       
    89 filters result sets of size 1. Thus, on a result set containing multiple
       
    90 entities, :class:`one_line_rset` makes the EntityRSSIconBox class non
       
    91 selectable. However for a result set with one entity, the `EntityRSSIconBox`
       
    92 class will have a higher score than `RSSIconBox`, which is what we wanted.
       
    93 
       
    94 Of course, once this is done, you have to:
       
    95 
       
    96 * fill in the call method of `EntityRSSIconBox`
       
    97 
       
    98 * provide the default implementation of the method returning the RSS stream url
       
    99   on :class:`~cubicweb.entities.AnyEntity`
       
   100 
       
   101 * redefine this method on `Blog`.
       
   102 
       
   103 
       
   104 When to use selectors?
       
   105 ~~~~~~~~~~~~~~~~~~~~~~
       
   106 
       
   107 Selectors are to be used whenever arises the need of dispatching on the shape or
       
   108 content of a result set or whatever else context (value in request form params,
       
   109 authenticated user groups, etc...). That is, almost all the time.
       
   110 
       
   111 Here is a quick example:
       
   112 
       
   113 .. sourcecode:: python
       
   114 
       
   115     class UserLink(component.Component):
       
   116 	'''if the user is the anonymous user, build a link to login else a link
       
   117 	to the connected user object with a logout link
       
   118 	'''
       
   119 	__regid__ = 'loggeduserlink'
       
   120 
       
   121 	def call(self):
       
   122 	    if self._cw.session.anonymous_session:
       
   123 		# display login link
       
   124 		...
       
   125 	    else:
       
   126 		# display a link to the connected user object with a loggout link
       
   127 		...
       
   128 
       
   129 The proper way to implement this with |cubicweb| is two have two different
       
   130 classes sharing the same identifier but with different selectors so you'll get
       
   131 the correct one according to the context.
       
   132 
       
   133 .. sourcecode:: python
       
   134 
       
   135     class UserLink(component.Component):
       
   136 	'''display a link to the connected user object with a loggout link'''
       
   137 	__regid__ = 'loggeduserlink'
       
   138 	__select__ = component.Component.__select__ & authenticated_user()
       
   139 
       
   140 	def call(self):
       
   141             # display useractions and siteactions
       
   142 	    ...
       
   143 
       
   144     class AnonUserLink(component.Component):
       
   145 	'''build a link to login'''
       
   146 	__regid__ = 'loggeduserlink'
       
   147 	__select__ = component.Component.__select__ & anonymous_user()
       
   148 
       
   149 	def call(self):
       
   150 	    # display login link
       
   151             ...
       
   152 
       
   153 The big advantage, aside readability once you're familiar with the
       
   154 system, is that your cube becomes much more easily customizable by
       
   155 improving componentization.
       
   156 
       
   157 
       
   158 .. _CustomPredicates:
       
   159 
       
   160 Defining your own predicates
       
   161 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
       
   162 
       
   163 .. autodocstring:: cubicweb.appobject::objectify_predicate
       
   164 
       
   165 In other cases, you can take a look at the following abstract base classes:
       
   166 
       
   167 .. autoclass:: cubicweb.predicates.ExpectedValuePredicate
       
   168 .. autoclass:: cubicweb.predicates.EClassPredicate
       
   169 .. autoclass:: cubicweb.predicates.EntityPredicate
       
   170 
       
   171 .. _DebuggingSelectors:
       
   172 
       
   173 Debugging selection
       
   174 ~~~~~~~~~~~~~~~~~~~
       
   175 
       
   176 Once in a while, one needs to understand why a view (or any application object)
       
   177 is, or is not selected appropriately. Looking at which predicates fired (or did
       
   178 not) is the way. The :class:`logilab.common.registry.traced_selection` context
       
   179 manager to help with that, *if you're running your instance in debug mode*.
       
   180 
       
   181 .. autoclass:: logilab.common.registry.traced_selection
       
   182 
       
   183 """
    19 """
   184 
    20 
   185 __docformat__ = "restructuredtext en"
    21 __docformat__ = "restructuredtext en"
   186 
    22 
   187 import logging
    23 import logging