1 .. VRegistry: |
|
2 |
|
3 The `VRegistry` |
|
4 --------------- |
|
5 |
|
6 The `VRegistry` can be seen as a two level dictionary. It contains all objects |
|
7 loaded dynamically to build a |cubicweb| application. Basically: |
|
8 |
|
9 * first level key return a *registry*. This key corresponds to the `__registry__` |
|
10 attribute of application object classes |
|
11 |
|
12 * second level key return a list of application objects which share the same |
|
13 identifier. This key corresponds to the `__regid__` attribute of application |
|
14 object classes. |
|
15 |
|
16 A *registry* hold a specific kind of application objects. You've for instance |
|
17 a registry for entity classes, another for views, etc... |
|
18 |
|
19 The `VRegistry` has two main responsibilities: |
|
20 |
|
21 - being the access point to all registries |
|
22 |
|
23 - handling the registration process at startup time, and during automatic |
|
24 reloading in debug mode. |
|
25 |
|
26 |
|
27 .. _AppObjectRecording: |
|
28 |
|
29 Managing the recording process |
|
30 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|
31 |
|
32 Details of the recording process |
|
33 ```````````````````````````````` |
|
34 |
|
35 .. index:: |
|
36 vregistry: registration_callback |
|
37 |
|
38 On startup, |cubicweb| have to load application objects defined in its library |
|
39 and in cubes used by the instance. Application objects from the library are |
|
40 loaded first, then those provided by cubes are loaded in an ordered way (e.g. if |
|
41 your cube depends on an other, objects from the dependancy will be loaded |
|
42 first). Cube's modules or packages where appobject are looked at is explained in |
|
43 :ref:`cubelayout`. |
|
44 |
|
45 For each module: |
|
46 |
|
47 * by default all objects are registered automatically |
|
48 |
|
49 * if some objects have to replace other objects, or be included only if some |
|
50 condition is true, you'll have to define a `registration_callback(vreg)` |
|
51 function in your module and explicitly register **all objects** in this module, |
|
52 using the api defined below. |
|
53 |
|
54 .. Note:: |
|
55 Once the function `registration_callback(vreg)` is implemented in a module, |
|
56 all the objects from this module have to be explicitly registered as it |
|
57 disables the automatic objects registration. |
|
58 |
|
59 |
|
60 API for objects registration |
|
61 ```````````````````````````` |
|
62 |
|
63 Here are the registration methods that you can use in the `registration_callback` |
|
64 to register your objects to the `VRegistry` instance given as argument (usually |
|
65 named `vreg`): |
|
66 |
|
67 .. automethod:: cubicweb.cwvreg.CubicWebVRegistry.register_all |
|
68 .. automethod:: cubicweb.cwvreg.CubicWebVRegistry.register_and_replace |
|
69 .. automethod:: cubicweb.cwvreg.CubicWebVRegistry.register |
|
70 .. automethod:: cubicweb.cwvreg.CubicWebVRegistry.register_if_interface_found |
|
71 .. automethod:: cubicweb.cwvreg.CubicWebVRegistry.unregister |
|
72 |
|
73 |
|
74 Examples |
|
75 ```````` |
|
76 .. sourcecode:: python |
|
77 |
|
78 # web/views/basecomponents.py |
|
79 def registration_callback(vreg): |
|
80 # register everything in the module except SeeAlsoComponent |
|
81 vreg.register_all(globals().values(), __name__, (SeeAlsoVComponent,)) |
|
82 # conditionally register SeeAlsoVComponent |
|
83 if 'see_also' in vreg.schema: |
|
84 vreg.register(SeeAlsoVComponent) |
|
85 |
|
86 In this example, we register all application object classes defined in the module |
|
87 except `SeeAlsoVComponent`. This class is then registered only if the 'see_also' |
|
88 relation type is defined in the instance'schema. |
|
89 |
|
90 .. sourcecode:: python |
|
91 |
|
92 # goa/appobjects/sessions.py |
|
93 def registration_callback(vreg): |
|
94 vreg.register(SessionsCleaner) |
|
95 # replace AuthenticationManager by GAEAuthenticationManager |
|
96 vreg.register_and_replace(GAEAuthenticationManager, AuthenticationManager) |
|
97 # replace PersistentSessionManager by GAEPersistentSessionManager |
|
98 vreg.register_and_replace(GAEPersistentSessionManager, PersistentSessionManager) |
|
99 |
|
100 In this example, we explicitly register classes one by one: |
|
101 |
|
102 * the `SessionCleaner` class |
|
103 * the `GAEAuthenticationManager` to replace the `AuthenticationManager` |
|
104 * the `GAEPersistentSessionManager` to replace the `PersistentSessionManager` |
|
105 |
|
106 If at some point we register a new appobject class in this module, it won't be |
|
107 registered at all without modification to the `registration_callback` |
|
108 implementation. The previous example will register it though, thanks to the call |
|
109 to the `register_all` method. |
|
110 |
|
111 .. _Selection: |
|
112 |
|
113 Runtime objects selection |
|
114 ~~~~~~~~~~~~~~~~~~~~~~~~~ |
|
115 |
|
116 Now that we've all application objects loaded, the question is : when I want some |
|
117 specific object, for instance the primary view for a given entity, how do I get |
|
118 the proper object ? This is what we call the **selection mechanism**. |
|
119 |
|
120 As explained in the :ref:`Concepts` section: |
|
121 |
|
122 * each application object has a **selector**, defined by its `__select__` class attribute |
|
123 |
|
124 * this selector is responsible to return a **score** for a given context |
|
125 |
|
126 - 0 score means the object doesn't apply to this context |
|
127 |
|
128 - else, the higher the score, the better the object suits the context |
|
129 |
|
130 * the object with the higher score is selected. |
|
131 |
|
132 .. Note:: |
|
133 |
|
134 When no score is higher than the others, an exception is raised in development |
|
135 mode to let you know that the engine was not able to identify the view to |
|
136 apply. This error is silenced in production mode and one of the objects with |
|
137 the higher score is picked. |
|
138 |
|
139 In such cases you would need to review your design and make sure your selectors |
|
140 or appobjects are properly defined. |
|
141 |
|
142 For instance, if you are selecting the primary (eg `__regid__ = 'primary'`) view (eg |
|
143 `__registry__ = 'views'`) for a result set containing a `Card` entity, 2 objects |
|
144 will probably be selectable: |
|
145 |
|
146 * the default primary view (`__select__ = implements('Any')`), meaning |
|
147 that the object is selectable for any kind of entity type |
|
148 |
|
149 * the specific `Card` primary view (`__select__ = implements('Card')`, |
|
150 meaning that the object is selectable for Card entities |
|
151 |
|
152 Other primary views specific to other entity types won't be selectable in this |
|
153 case. Among selectable objects, the implements selector will return a higher |
|
154 score than the second view since it's more specific, so it will be selected as |
|
155 expected. |
|
156 |
|
157 .. _SelectionAPI: |
|
158 |
|
159 API for objects selections |
|
160 `````````````````````````` |
|
161 |
|
162 Here is the selection API you'll get on every registry. Some of them, as the |
|
163 'etypes' registry, containing entity classes, extend it. In those methods, |
|
164 `*args, **kwargs` is what we call the **context**. Those arguments are given to |
|
165 selectors that will inspect there content and return a score accordingly. |
|
166 |
|
167 .. automethod:: cubicweb.vregistry.Registry.select |
|
168 |
|
169 .. automethod:: cubicweb.vregistry.Registry.select_or_none |
|
170 |
|
171 .. automethod:: cubicweb.vregistry.Registry.possible_objects |
|
172 |
|
173 .. automethod:: cubicweb.vregistry.Registry.object_by_id |
|
174 |
|
175 |
|
176 .. _Selectors: |
|
177 |
|
178 Selectors |
|
179 --------- |
|
180 |
|
181 Using and combining existant selectors |
|
182 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|
183 |
|
184 You can combine selectors using the `&`, `|` and `~` operators. |
|
185 |
|
186 When two selectors are combined using the `&` operator (formerly `chainall`), it |
|
187 means that both should return a positive score. On success, the sum of scores is returned. |
|
188 |
|
189 When two selectors are combined using the `|` operator (former `chainfirst`), it |
|
190 means that one of them should return a positive score. On success, the first |
|
191 positive score is returned. |
|
192 |
|
193 You can also "negate" a selector by precedeing it by the `~` unary operator. |
|
194 |
|
195 Of course you can use parens to balance expressions. |
|
196 |
|
197 .. Note: |
|
198 When one chains selectors, the final score is the sum of the score of each |
|
199 individual selector (unless one of them returns 0, in which case the object is |
|
200 non selectable) |
|
201 |
|
202 |
|
203 Example |
|
204 ~~~~~~~ |
|
205 |
|
206 The goal: when on a Blog, one wants the RSS link to refer to blog entries, not to |
|
207 the blog entity itself. |
|
208 |
|
209 To do that, one defines a method on entity classes that returns the RSS stream |
|
210 url for a given entity. The default implementation on |
|
211 :class:`~cubicweb.entities.AnyEntity` (the generic entity class used as base for |
|
212 all others) and a specific implementation on Blog will do what we want. |
|
213 |
|
214 But when we have a result set containing several Blog entities (or different |
|
215 entities), we don't know on which entity to call the aforementioned method. In |
|
216 this case, we keep the generic behaviour. |
|
217 |
|
218 Hence we have two cases here, one for a single-entity rsets, the other for |
|
219 multi-entities rsets. |
|
220 |
|
221 In web/views/boxes.py lies the RSSIconBox class. Look at its selector :: |
|
222 |
|
223 class RSSIconBox(ExtResourcesBoxTemplate): |
|
224 """just display the RSS icon on uniform result set""" |
|
225 __select__ = ExtResourcesBoxTemplate.__select__ & non_final_entity() |
|
226 |
|
227 It takes into account: |
|
228 |
|
229 * the inherited selection criteria (one has to look them up in the class |
|
230 hierarchy to know the details) |
|
231 |
|
232 * :class:`~cubicweb.selectors.non_final_entity`, which filters on result sets |
|
233 containing non final entities (a 'final entity' being synonym for entity |
|
234 attributes type, eg `String`, `Int`, etc) |
|
235 |
|
236 This matches our second case. Hence we have to provide a specific component for |
|
237 the first case:: |
|
238 |
|
239 class EntityRSSIconBox(RSSIconBox): |
|
240 """just display the RSS icon on uniform result set for a single entity""" |
|
241 __select__ = RSSIconBox.__select__ & one_line_rset() |
|
242 |
|
243 Here, one adds the :class:`~cubicweb.selectors.one_line_rset` selector, which |
|
244 filters result sets of size 1. Thus, on a result set containing multiple |
|
245 entities, :class:`one_line_rset` makes the EntityRSSIconBox class non |
|
246 selectable. However for a result set with one entity, the `EntityRSSIconBox` |
|
247 class will have a higher score than `RSSIconBox`, which is what we wanted. |
|
248 |
|
249 Of course, once this is done, you have to: |
|
250 |
|
251 * fill in the call method of `EntityRSSIconBox` |
|
252 |
|
253 * provide the default implementation of the method returning the RSS stream url |
|
254 on :class:`~cubicweb.entities.AnyEntity` |
|
255 |
|
256 * redefine this method on `Blog`. |
|
257 |
|
258 |
|
259 When to use selectors? |
|
260 ~~~~~~~~~~~~~~~~~~~~~~ |
|
261 |
|
262 Selectors are to be used whenever arises the need of dispatching on the shape or |
|
263 content of a result set or whatever else context (value in request form params, |
|
264 authenticated user groups, etc...). That is, almost all the time. |
|
265 |
|
266 .. XXX add and example of a single view w/ big "if" inside splitted into two views with appropriate selectors. |
|
267 |
|
268 |
|
269 .. _CustomSelectors: |
|
270 |
|
271 Defining your own selectors |
|
272 ~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|
273 .. autoclass:: cubicweb.appobject.Selector |
|
274 :members: __call__ |
|
275 |
|
276 .. autofunction:: cubicweb.appobject.objectify_selector |
|
277 |
|
278 Selectors __call__ should *always* return a positive integer, and shall never |
|
279 return `None`. |
|
280 |
|
281 Useful abstract base classes for 'entity' selectors: |
|
282 |
|
283 .. autoclass:: cubicweb.selectors.EClassSelector |
|
284 .. autoclass:: cubicweb.selectors.EntitySelector |
|
285 |
|
286 Also, think to use the `lltrace` decorator on your selector class' :meth:`__call__` method |
|
287 or below the :func:`objectify_selector` decorator of your selector function so it gets |
|
288 traceable when :class:`traced_selection` is activated (see :ref:DebuggingSelectors). |
|
289 |
|
290 .. autofunction:: cubicweb.selectors.lltrace |
|
291 |
|
292 |
|
293 .. _DebuggingSelectors: |
|
294 |
|
295 Debugging selection |
|
296 ~~~~~~~~~~~~~~~~~~~ |
|
297 |
|
298 Once in a while, one needs to understand why a view (or any AppObject) is, or is |
|
299 not selected appropriately. Looking at which selectors fired (or did not) is the |
|
300 way. There exists a traced_selection context manager to help with that, *if |
|
301 you're running your instance in debug mode*. |
|
302 |
|
303 Here is an example: |
|
304 |
|
305 .. sourcecode:: python |
|
306 |
|
307 from cubicweb.selectors import traced_selection |
|
308 with traced_selection(): |
|
309 mycomp = self._cw.vreg['views'].select('wfhistory', self._cw, rset=rset) |
|
310 |
|
311 Don't forget the 'from __future__ import with_statement' at the module |
|
312 top-level if you're using python 2.5. |
|
313 |
|
314 This will yield additional WARNINGs in the logs, like this:: |
|
315 |
|
316 2009-01-09 16:43:52 - (cubicweb.selectors) WARNING: selector one_line_rset returned 0 for <class 'cubicweb.web.views.basecomponents.WFHistoryVComponent'> |
|
317 |
|
318 You can also give to traced_selection the registry ids of objects on which to debug |
|
319 you want to debug selection ('wfhistory' in the example above). |
|
320 |
|
321 |
|
322 |
|
323 .. |cubicweb| replace:: *CubicWeb* |
|