182 |
186 |
183 The components of the engine are: |
187 The components of the engine are: |
184 |
188 |
185 * a frontal web (only twisted is available so far), transparent for dynamic objects |
189 * a frontal web (only twisted is available so far), transparent for dynamic objects |
186 * an object that encapsulates the configuration |
190 * an object that encapsulates the configuration |
187 * a `vregistry` (`cubicweb.cwvreg`) containing the dynamic objects loaded automatically |
191 * a `registry` (`cubicweb.cwvreg`) containing the dynamic objects loaded automatically |
188 |
192 |
189 |
193 Every *appobject* may access to the instance configuration using its *config* attribute |
190 Details of a recording process |
194 and to the registry using its *vreg* attribute. |
191 ------------------------------ |
195 |
192 |
196 Details of the recording process |
193 At startup, the `vregistry` or registers base, inspects a number of directories |
197 -------------------------------- |
|
198 |
|
199 At startup, the `registry` or registers base, inspects a number of directories |
194 looking for compatible classes definition. After a recording process, the objects |
200 looking for compatible classes definition. After a recording process, the objects |
195 are assigned to registers so that they can be selected dynamically while the |
201 are assigned to registers so that they can be selected dynamically while the |
196 application is running. |
202 application is running. |
197 |
203 |
198 The base class of those objects is `AppRsetObject` (module `cubicweb.common.appobject`). |
204 The base class of those objects is `AppRsetObject` (module `cubicweb.common.appobject`). |
|
205 |
|
206 XXX registers example |
|
207 XXX actual details of the recording process! |
|
208 |
|
209 Runtime objects selection |
|
210 ------------------------- |
|
211 XXX tell why it's a cw foundation! |
|
212 |
|
213 Application objects are stored in the registry using a two level hierarchy : |
|
214 |
|
215 object's `__registry__` : object's `id` : [list of app objects] |
|
216 |
|
217 The following rules are applied to select an object given a register and an id and an input context: |
|
218 * each object has a selector |
|
219 - its selector may be derivated from a set of basic (or not :) |
|
220 selectors using `chainall` or `chainfirst` combinators |
|
221 * a selector return a score >= 0 |
|
222 * a score of 0 means the objects can't be applied to the input context |
|
223 * the object with the greatest score is selected. If multiple objects have an |
|
224 identical score, one of them is selected randomly (this is usually a bug) |
|
225 |
|
226 The object's selector is the `__selector__` class method on the object's class. |
|
227 |
|
228 The score is used to choose the most pertinent objects where there are more than |
|
229 one selectable object. For instance, if you're selecting the primary |
|
230 (eg `id = 'primary'`) view (eg `__registry__ = 'view'`) for a result set containing |
|
231 a `Card` entity, 2 objects will probably be selectable: |
|
232 |
|
233 * the default primary view (`accepts = 'Any'`) |
|
234 * the specific `Card` primary view (`accepts = 'Card'`) |
|
235 |
|
236 This is because primary views are using the `accept_selector` which is considering the |
|
237 `accepts` class attribute of the object's class. Other primary views specific to other |
|
238 entity types won't be selectable in this case. And among selectable objects, the |
|
239 accept selector will return a higher score the the second view since it's more |
|
240 specific, so it will be selected as expected. |
|
241 |
|
242 Usually, you won't define it directly but by defining the `__selectors__` tuple |
|
243 on the class, with :: |
|
244 |
|
245 __selectors__ = (sel1, sel2) |
|
246 |
|
247 which is equivalent to :: |
|
248 |
|
249 __selector__ = chainall(sel1, sel2) |
|
250 |
|
251 The former is prefered since it's shorter and it's ease overriding in |
|
252 subclasses (you have access to sub-selectors instead of the wrapping function). |
|
253 |
|
254 :chainall(selectors...): if one selector return 0, return 0, else return the sum of scores |
|
255 |
|
256 :chainfirst(selectors...): return the score of the first selector which has a non zero score |
|
257 |
|
258 XXX describe standard selector (link to generated api doc!) |
|
259 |
|
260 Example |
|
261 ~~~~~~~ |
|
262 Le but final : quand on est sur un Blog, on veut que le lien rss de celui-ci pointe |
|
263 vers les entrées de ce blog, non vers l'entité blog elle-même. |
|
264 |
|
265 L'idée générale pour résoudre ça : on définit une méthode sur les classes d'entité |
|
266 qui renvoie l'url du flux rss pour l'entité en question. Avec une implémentation |
|
267 par défaut sur AnyEntity et une implémentation particulière sur Blog qui fera ce |
|
268 qu'on veut. |
|
269 |
|
270 La limitation : on est embêté dans le cas ou par ex. on a un result set qui contient |
|
271 plusieurs entités Blog (ou autre chose), car on ne sait pas sur quelle entité appeler |
|
272 la méthode sus-citée. Dans ce cas, on va conserver le comportement actuel (eg appel |
|
273 à limited_rql) |
|
274 |
|
275 Donc : on veut deux cas ici, l'un pour un rset qui contient une et une seule entité, |
|
276 l'autre pour un rset qui contient plusieurs entité. |
|
277 |
|
278 Donc... On a déja dans web/views/boxes.py la classe RSSIconBox qui fonctionne. Son |
|
279 sélecteur :: |
|
280 |
|
281 class RSSIconBox(ExtResourcesBoxTemplate): |
|
282 """just display the RSS icon on uniform result set""" |
|
283 __selectors__ = ExtResourcesBoxTemplate.__selectors__ + (nfentity_selector,) |
|
284 |
|
285 |
|
286 indique qu'il prend en compte : |
|
287 |
|
288 * les conditions d'apparition de la boite (faut remonter dans les classes parentes |
|
289 pour voir le détail) |
|
290 * nfentity_selector, qui filtre sur des rset contenant une liste d'entité non finale |
|
291 |
|
292 ça correspond donc à notre 2eme cas. Reste à fournir un composant plus spécifique |
|
293 pour le 1er cas :: |
|
294 |
|
295 class EntityRSSIconBox(RSSIconBox): |
|
296 """just display the RSS icon on uniform result set for a single entity""" |
|
297 __selectors__ = RSSIconBox.__selectors__ + (onelinerset_selector,) |
|
298 |
|
299 |
|
300 Ici, on ajoute onelinerset_selector, qui filtre sur des rset de taille 1. Il faut |
|
301 savoir que quand on chaine des selecteurs, le score final est la somme des scores |
|
302 renvoyés par chaque sélecteur (sauf si l'un renvoie zéro, auquel cas l'objet est |
|
303 non sélectionnable). Donc ici, sur un rset avec plusieurs entités, onelinerset_selector |
|
304 rendra la classe EntityRSSIconBox non sélectionnable, et on obtiendra bien la |
|
305 classe RSSIconBox. Pour un rset avec une entité, la classe EntityRSSIconBox aura un |
|
306 score supérieur à RSSIconBox et c'est donc bien elle qui sera sélectionnée. |
|
307 |
|
308 Voili voilou, il reste donc pour finir tout ça : |
|
309 |
|
310 * à définir le contenu de la méthode call de EntityRSSIconBox |
|
311 * fournir l'implémentation par défaut de la méthode renvoyant l'url du flux rss sur |
|
312 AnyEntity |
|
313 * surcharger cette methode dans blog.Blog |
|
314 |
|
315 When to use selectors? |
|
316 ~~~~~~~~~~~~~~~~~~~~~~ |
|
317 Il faut utiliser les sélecteurs pour faire des choses différentes en |
|
318 fonction de ce qu'on a en entrée. Dès qu'on a un "if" qui teste la |
|
319 nature de `self.rset` dans un objet, il faut très sérieusement se |
|
320 poser la question s'il ne vaut pas mieux avoir deux objets différent |
|
321 avec des sélecteurs approprié. |
|
322 |
|
323 If this is so fundamental, why don't I see them more often? |
|
324 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|
325 Because you're usually using base classes which are hiding the plumbing |
|
326 of __registry__ (almost always), id (often when using "standard" object), |
|
327 register and selector. |
199 |
328 |
200 API Python/RQL |
329 API Python/RQL |
201 -------------- |
330 -------------- |
202 |
331 |
203 Inspired from the standard db-api, with a Connection object having the methods |
332 Inspired from the standard db-api, with a Connection object having the methods |