1 The VRegistry |
|
2 -------------- |
|
3 |
|
4 The recording process on startup |
|
5 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|
6 |
|
7 Details of the recording process |
|
8 ```````````````````````````````` |
|
9 |
|
10 .. index:: |
|
11 vregistry: registration_callback |
|
12 |
|
13 On startup, |cubicweb| have to fill the vregistry with appobjects defined |
|
14 in its library and in cubes used by the instance. Appobjects from the library |
|
15 are loaded first, then appobjects provided by cubes are loaded in an ordered |
|
16 way (e.g. if your cube depends on an other, appobjects from the dependancy will |
|
17 be loaded first). Cube's modules or packages where appobject are looked at is explained |
|
18 in :ref:`cubelayout`. |
|
19 |
|
20 For each module: |
|
21 |
|
22 * by default all objects are registered automatically |
|
23 |
|
24 * if some objects have to replace other objects or be included only if a |
|
25 condition is true, you'll have to define a `registration_callback(vreg)` |
|
26 function in your module and explicitly register *all objects* in this |
|
27 module, using the vregistry api defined below. |
|
28 |
|
29 .. note:: |
|
30 Once the function `registration_callback(vreg)` is implemented, all the objects |
|
31 have to be explicitly registered as it disables the automatic object registering. |
|
32 |
|
33 |
|
34 API d'enregistrement des objets |
|
35 ``````````````````````````````` |
|
36 .. automethod:: cubicweb.cwvreg.CubicWebVRegistry.register_all |
|
37 .. automethod:: cubicweb.cwvreg.CubicWebVRegistry.register_and_replace |
|
38 .. automethod:: cubicweb.cwvreg.CubicWebVRegistry.register |
|
39 .. automethod:: cubicweb.cwvreg.CubicWebVRegistry.register_if_interface_found |
|
40 .. automethod:: cubicweb.cwvreg.CubicWebVRegistry.unregister |
|
41 |
|
42 |
|
43 Examples |
|
44 ```````` |
|
45 .. sourcecode:: python |
|
46 |
|
47 # web/views/basecomponents.py |
|
48 def registration_callback(vreg): |
|
49 # register everything in the module except SeeAlsoComponent |
|
50 vreg.register_all(globals().values(), __name__, (SeeAlsoVComponent,)) |
|
51 # conditionally register SeeAlsoVComponent |
|
52 if 'see_also' in vreg.schema: |
|
53 vreg.register(SeeAlsoVComponent) |
|
54 |
|
55 # goa/appobjects/sessions.py |
|
56 def registration_callback(vreg): |
|
57 vreg.register(SessionsCleaner) |
|
58 # replace AuthenticationManager by GAEAuthenticationManager |
|
59 vreg.register_and_replace(GAEAuthenticationManager, AuthenticationManager) |
|
60 # replace PersistentSessionManager by GAEPersistentSessionManager |
|
61 vreg.register_and_replace(GAEPersistentSessionManager, PersistentSessionManager) |
|
62 |
|
63 |
|
64 Runtime objects selection |
|
65 ~~~~~~~~~~~~~~~~~~~~~~~~~ |
|
66 |
|
67 Using and combining existant selectors |
|
68 `````````````````````````````````````` |
|
69 |
|
70 The object's selector is defined by its `__select__` class attribute. |
|
71 |
|
72 When two selectors are combined using the `&` operator (formerly `chainall`), it |
|
73 means that both should return a positive score. On success, the sum of scores is returned. |
|
74 |
|
75 When two selectors are combined using the `|` operator (former `chainfirst`), it |
|
76 means that one of them should return a positive score. On success, the first |
|
77 positive score is returned. |
|
78 |
|
79 You can also "negate" a selector by precedeing it by the `~` operator. |
|
80 |
|
81 Of course you can use paren to balance expressions. |
|
82 |
|
83 |
|
84 For instance, if you are selecting the primary (eg `__regid__ = 'primary'`) view (eg |
|
85 `__registry__ = 'view'`) for a result set containing a `Card` entity, 2 objects |
|
86 will probably be selectable: |
|
87 |
|
88 * the default primary view (`__select__ = implements('Any')`), meaning |
|
89 that the object is selectable for any kind of entity type |
|
90 |
|
91 * the specific `Card` primary view (`__select__ = implements('Card')`, |
|
92 meaning that the object is selectable for Card entities |
|
93 |
|
94 Other primary views specific to other entity types won't be selectable |
|
95 in this case. Among selectable objects, the implements selector will |
|
96 return a higher score than the second view since it's more specific, |
|
97 so it will be selected as expected. |
|
98 |
|
99 |
|
100 Example |
|
101 ```````` |
|
102 |
|
103 The goal: when on a Blog, one wants the RSS link to refer to blog |
|
104 entries, not to the blog entity itself. |
|
105 |
|
106 To do that, one defines a method on entity classes that returns the |
|
107 RSS stream url for a given entity. The default implementation on |
|
108 AnyEntity and a specific implementation on Blog will do what we want. |
|
109 |
|
110 But when we have a result set containing several Blog entities (or |
|
111 different entities), we don't know on which entity to call the |
|
112 aforementioned method. In this case, we keep the current behaviour |
|
113 (e.g : call to limited_rql). |
|
114 |
|
115 Hence we have two cases here, one for a single-entity rsets, the other |
|
116 for multi-entities rsets. |
|
117 |
|
118 In web/views/boxes.py lies the RSSIconBox class. Look at its selector :: |
|
119 |
|
120 class RSSIconBox(ExtResourcesBoxTemplate): |
|
121 """just display the RSS icon on uniform result set""" |
|
122 __select__ = ExtResourcesBoxTemplate.__select__ & non_final_entity() |
|
123 |
|
124 It takes into account: |
|
125 |
|
126 * the inherited selection criteria (one has to look them up in the |
|
127 class hierarchy to know the details) |
|
128 |
|
129 * non_final_entity, which filters on rsets containing non final |
|
130 entities (a 'final entity' being synonym for entity attribute) |
|
131 |
|
132 This matches our second case. Hence we have to provide a specific |
|
133 component for the first case:: |
|
134 |
|
135 class EntityRSSIconBox(RSSIconBox): |
|
136 """just display the RSS icon on uniform result set for a single entity""" |
|
137 __select__ = RSSIconBox.__select__ & one_line_rset() |
|
138 |
|
139 Here, one adds the one_line_rset selector, which filters result sets |
|
140 of size 1. When one chains selectors, the final score is the sum of |
|
141 the score of each individual selector (unless one of them returns 0, |
|
142 in which case the object is non selectable). Thus, on a multiple |
|
143 entities selector, one_line_rset makes the EntityRSSIconBox class non |
|
144 selectable. For an rset with one entity, the EntityRSSIconBox class |
|
145 will have a higher score then RSSIconBox, which is what we wanted. |
|
146 |
|
147 Of course, once this is done, you have to: |
|
148 |
|
149 * fill in the call method of EntityRSSIconBox |
|
150 |
|
151 * provide the default implementation of the method returning the RSS |
|
152 stream url on AnyEntity |
|
153 |
|
154 * redefine this method on Blog. |
|
155 |
|
156 When to use selectors? |
|
157 `````````````````````` |
|
158 |
|
159 Selectors are to be used whenever arises the need of dispatching on the shape or |
|
160 content of a result set or whatever else context (value in request form params, |
|
161 authenticated user groups, etc...). That is, almost all the time. |
|
162 |
|
163 XXX add and example of a single view w/ big "if" inside splitted into two views |
|
164 with appropriate selectors. |
|
165 |
|
166 |
|
167 .. CustomSelectors_ |
|
168 |
|
169 Defining your own selectors |
|
170 ``````````````````````````` |
|
171 .. autoclass:: cubicweb.appobject.Selector |
|
172 :members: __call__ |
|
173 |
|
174 .. autofunction:: cubicweb.appobject.objectify_selector |
|
175 .. autofunction:: cubicweb.selectors.lltrace |
|
176 |
|
177 Selectors __call__ should *always* return a positive integer, and shall never |
|
178 return `None`. |
|
179 |
|
180 Useful abstract base classes for 'entity' selectors: |
|
181 |
|
182 .. autoclass:: cubicweb.selectors.EClassSelector |
|
183 .. autoclass:: cubicweb.selectors.EntitySelector |
|
184 |
|
185 |
|
186 Debugging |
|
187 ````````` |
|
188 |
|
189 Once in a while, one needs to understand why a view (or any AppObject) |
|
190 is, or is not selected appropriately. Looking at which selectors fired |
|
191 (or did not) is the way. There exists a traced_selection context |
|
192 manager to help with that, *if you're running your instance in debug mode*. |
|
193 |
|
194 Here is an example: |
|
195 |
|
196 .. sourcecode:: python |
|
197 |
|
198 from cubicweb.selectors import traced_selection |
|
199 with traced_selection(): |
|
200 mycomp = self._cw.vreg['views'].select('wfhistory', self._cw, rset=rset) |
|
201 |
|
202 Don't forget the 'from __future__ import with_statement' at the module |
|
203 top-level if you're using python 2.5. |
|
204 |
|
205 This will yield additional WARNINGs in the logs, like this:: |
|
206 |
|
207 2009-01-09 16:43:52 - (cubicweb.selectors) WARNING: selector one_line_rset returned 0 for <class 'cubicweb.web.views.basecomponents.WFHistoryVComponent'> |
|
208 |
|
209 You can also give to traced_selection the registry ids of objects on which to debug |
|
210 you want to debug selection ('wfhistory' in the example above). |
|
211 |
|
212 Also, if you're using python 2.4, which as no 'with' yet, you'll have to to it |
|
213 the following way: |
|
214 |
|
215 .. sourcecode:: python |
|
216 |
|
217 from cubicweb import selectors |
|
218 selectors.TRACED_OIDS = ('wfhistory',) |
|
219 mycomp = self._cw.vreg['views'].select('wfhistory', self._cw, rset=rset) |
|
220 selectors.TRACED_OIDS = () |
|