1 """This file contains some basic selectors required by application objects. |
1 # :organization: Logilab |
2 |
2 # :copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2. |
3 A selector is responsible to score how well an object may be used with a |
3 # :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr |
4 given context by returning a score. |
4 # :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses |
5 |
5 """.. _Selectors: |
6 In CubicWeb Usually the context consists for a request object, a result set |
6 |
7 or None, a specific row/col in the result set, etc... |
7 Selectors |
8 |
8 --------- |
9 |
9 |
10 If you have trouble with selectors, especially if the objet (typically |
10 Using and combining existant selectors |
11 a view or a component) you want to use is not selected and you want to |
11 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
12 know which one(s) of its selectors fail (e.g. returns 0), you can use |
12 |
13 `traced_selection` or even direclty `TRACED_OIDS`. |
13 You can combine selectors using the `&`, `|` and `~` operators. |
14 |
14 |
15 `TRACED_OIDS` is a tuple of traced object ids. The special value |
15 When two selectors are combined using the `&` operator (formerly `chainall`), it |
16 'all' may be used to log selectors for all objects. |
16 means that both should return a positive score. On success, the sum of scores is returned. |
17 |
17 |
18 For instance, say that the following code yields a `NoSelectableObject` |
18 When two selectors are combined using the `|` operator (former `chainfirst`), it |
19 exception:: |
19 means that one of them should return a positive score. On success, the first |
20 |
20 positive score is returned. |
21 self.view('calendar', myrset) |
21 |
22 |
22 You can also "negate" a selector by precedeing it by the `~` unary operator. |
23 You can log the selectors involved for *calendar* by replacing the line |
23 |
24 above by:: |
24 Of course you can use parens to balance expressions. |
25 |
25 |
26 from cubicweb.selectors import traced_selection |
26 .. Note: |
27 with traced_selection(): |
27 When one chains selectors, the final score is the sum of the score of each |
28 self.view('calendar', myrset) |
28 individual selector (unless one of them returns 0, in which case the object is |
29 |
29 non selectable) |
30 With python 2.5, think to add: |
30 |
31 |
31 |
32 from __future__ import with_statement |
32 Example |
33 |
33 ~~~~~~~ |
34 at the top of your module. |
34 |
35 |
35 The goal: when on a Blog, one wants the RSS link to refer to blog entries, not to |
36 :organization: Logilab |
36 the blog entity itself. |
37 :copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2. |
37 |
38 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr |
38 To do that, one defines a method on entity classes that returns the RSS stream |
39 :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses |
39 url for a given entity. The default implementation on |
|
40 :class:`~cubicweb.entities.AnyEntity` (the generic entity class used as base for |
|
41 all others) and a specific implementation on Blog will do what we want. |
|
42 |
|
43 But when we have a result set containing several Blog entities (or different |
|
44 entities), we don't know on which entity to call the aforementioned method. In |
|
45 this case, we keep the generic behaviour. |
|
46 |
|
47 Hence we have two cases here, one for a single-entity rsets, the other for |
|
48 multi-entities rsets. |
|
49 |
|
50 In web/views/boxes.py lies the RSSIconBox class. Look at its selector: |
|
51 |
|
52 .. sourcecode:: python |
|
53 |
|
54 class RSSIconBox(ExtResourcesBoxTemplate): |
|
55 '''just display the RSS icon on uniform result set''' |
|
56 __select__ = ExtResourcesBoxTemplate.__select__ & non_final_entity() |
|
57 |
|
58 It takes into account: |
|
59 |
|
60 * the inherited selection criteria (one has to look them up in the class |
|
61 hierarchy to know the details) |
|
62 |
|
63 * :class:`~cubicweb.selectors.non_final_entity`, which filters on result sets |
|
64 containing non final entities (a 'final entity' being synonym for entity |
|
65 attributes type, eg `String`, `Int`, etc) |
|
66 |
|
67 This matches our second case. Hence we have to provide a specific component for |
|
68 the first case: |
|
69 |
|
70 .. sourcecode:: python |
|
71 |
|
72 class EntityRSSIconBox(RSSIconBox): |
|
73 '''just display the RSS icon on uniform result set for a single entity''' |
|
74 __select__ = RSSIconBox.__select__ & one_line_rset() |
|
75 |
|
76 Here, one adds the :class:`~cubicweb.selectors.one_line_rset` selector, which |
|
77 filters result sets of size 1. Thus, on a result set containing multiple |
|
78 entities, :class:`one_line_rset` makes the EntityRSSIconBox class non |
|
79 selectable. However for a result set with one entity, the `EntityRSSIconBox` |
|
80 class will have a higher score than `RSSIconBox`, which is what we wanted. |
|
81 |
|
82 Of course, once this is done, you have to: |
|
83 |
|
84 * fill in the call method of `EntityRSSIconBox` |
|
85 |
|
86 * provide the default implementation of the method returning the RSS stream url |
|
87 on :class:`~cubicweb.entities.AnyEntity` |
|
88 |
|
89 * redefine this method on `Blog`. |
|
90 |
|
91 |
|
92 When to use selectors? |
|
93 ~~~~~~~~~~~~~~~~~~~~~~ |
|
94 |
|
95 Selectors are to be used whenever arises the need of dispatching on the shape or |
|
96 content of a result set or whatever else context (value in request form params, |
|
97 authenticated user groups, etc...). That is, almost all the time. |
|
98 |
|
99 Here is a quick example: |
|
100 |
|
101 .. sourcecode:: python |
|
102 |
|
103 class UserLink(component.Component): |
|
104 '''if the user is the anonymous user, build a link to login else a link |
|
105 to the connected user object with a loggout link |
|
106 ''' |
|
107 __regid__ = 'loggeduserlink' |
|
108 |
|
109 def call(self): |
|
110 if self._cw.cnx.anonymous_connection: |
|
111 # display login link |
|
112 ... |
|
113 else: |
|
114 # display a link to the connected user object with a loggout link |
|
115 ... |
|
116 |
|
117 The proper way to implement this with |cubicweb| is two have two different |
|
118 classes sharing the same identifier but with different selectors so you'll get |
|
119 the correct one according to the context: |
|
120 |
|
121 |
|
122 class UserLink(component.Component): |
|
123 '''display a link to the connected user object with a loggout link''' |
|
124 __regid__ = 'loggeduserlink' |
|
125 __select__ = component.Component.__select__ & authenticated_user() |
|
126 |
|
127 def call(self): |
|
128 # display useractions and siteactions |
|
129 ... |
|
130 |
|
131 class AnonUserLink(component.Component): |
|
132 '''build a link to login''' |
|
133 __regid__ = 'loggeduserlink' |
|
134 __select__ = component.Component.__select__ & anonymous_user() |
|
135 |
|
136 def call(self): |
|
137 # display login link |
|
138 ... |
|
139 |
|
140 The big advantage, aside readibily once you're familiar with the system, is that |
|
141 your cube becomes much more easily customizable by improving componentization. |
|
142 |
|
143 |
|
144 .. _CustomSelectors: |
|
145 |
|
146 Defining your own selectors |
|
147 ~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|
148 |
|
149 .. autodocstring:: cubicweb.appobject::objectify_selector |
|
150 |
|
151 In other case, you can take a look at the following abstract base classes: |
|
152 |
|
153 .. autoclass:: cubicweb.selectors.ExpectedValueSelector |
|
154 .. autoclass:: cubicweb.selectors.EClassSelector |
|
155 .. autoclass:: cubicweb.selectors.EntitySelector |
|
156 |
|
157 Also, think to use the :func:`lltrace` decorator on your selector class' :meth:`__call__` method |
|
158 or below the :func:`objectify_selector` decorator of your selector function so it gets |
|
159 traceable when :class:`traced_selection` is activated (see :ref:`DebuggingSelectors`). |
|
160 |
|
161 .. autofunction:: cubicweb.selectors.lltrace |
|
162 |
|
163 .. Note:: |
|
164 Selectors __call__ should *always* return a positive integer, and shall never |
|
165 return `None`. |
|
166 |
|
167 |
|
168 .. _DebuggingSelectors: |
|
169 |
|
170 Debugging selection |
|
171 ~~~~~~~~~~~~~~~~~~~ |
|
172 |
|
173 Once in a while, one needs to understand why a view (or any application object) |
|
174 is, or is not selected appropriately. Looking at which selectors fired (or did |
|
175 not) is the way. The :class:`cubicweb.selectors.traced_selection` context |
|
176 manager to help with that, *if you're running your instance in debug mode*. |
|
177 |
|
178 .. autoclass:: cubicweb.selectors.traced_selection |
|
179 |
|
180 |
|
181 .. |cubicweb| replace:: *CubicWeb* |
40 """ |
182 """ |
41 __docformat__ = "restructuredtext en" |
183 __docformat__ = "restructuredtext en" |
42 |
184 |
43 import logging |
185 import logging |
44 from warnings import warn |
186 from warnings import warn |