1 .. -*- coding: utf-8 -*- |
|
2 |
|
3 .. _datamodel_definition: |
|
4 |
|
5 Yams *schema* |
|
6 ------------- |
|
7 |
|
8 The **schema** is the core piece of a *CubicWeb* instance as it |
|
9 defines and handles the data model. It is based on entity types that |
|
10 are either already defined in `Yams`_ and the *CubicWeb* standard |
|
11 library; or more specific types defined in cubes. The schema for a |
|
12 cube is defined in a `schema` python module or package. |
|
13 |
|
14 .. _`Yams`: http://www.logilab.org/project/yams |
|
15 |
|
16 .. _datamodel_overview: |
|
17 |
|
18 Overview |
|
19 ~~~~~~~~ |
|
20 |
|
21 The core idea of the yams schema is not far from the classical |
|
22 `Entity-relationship`_ model. But while an E/R model (or `logical |
|
23 model`) traditionally has to be manually translated to a lower-level |
|
24 data description language (such as the SQL `create table` |
|
25 sublanguage), also often described as the `physical model`, no such |
|
26 step is required with |yams| and |cubicweb|. |
|
27 |
|
28 .. _`Entity-relationship`: http://en.wikipedia.org/wiki/Entity-relationship_model |
|
29 |
|
30 This is because in addition to high-level, logical |yams| models, one |
|
31 uses the |rql| data manipulation language to query, insert, update and |
|
32 delete data. |rql| abstracts as much of the underlying SQL database as |
|
33 a |yams| schema abstracts from the physical layout. The vagaries of |
|
34 SQL are avoided. |
|
35 |
|
36 As a bonus point, such abstraction make it quite comfortable to build |
|
37 or use different backends to which |rql| queries apply. |
|
38 |
|
39 So, as in the E/R formalism, the building blocks are ``entities`` |
|
40 (:ref:`EntityType`), ``relationships`` (:ref:`RelationType`, |
|
41 :ref:`RelationDefinition`) and ``attributes`` (handled like relation |
|
42 with |yams|). |
|
43 |
|
44 Let us detail a little the divergences between E/R and |yams|: |
|
45 |
|
46 * all relationship are binary which means that to represent a |
|
47 non-binary relationship, one has to use an entity, |
|
48 * relationships do not support attributes (yet, see: |
|
49 http://www.cubicweb.org/ticket/341318), hence the need to reify it |
|
50 as an entity if need arises, |
|
51 * all entities have an `eid` attribute (an integer) that is its |
|
52 primary key (but it is possible to declare uniqueness on other |
|
53 attributes) |
|
54 |
|
55 Also |yams| supports the notions of: |
|
56 |
|
57 * entity inheritance (quite experimental yet, and completely |
|
58 undocumented), |
|
59 * relation type: that is, relationships can be established over a set |
|
60 of couple of entity types (henre the distinction made between |
|
61 `RelationType` and `RelationDefinition` below) |
|
62 |
|
63 Finally |yams| has a few concepts of its own: |
|
64 |
|
65 * relationships being oriented and binary, we call the left hand |
|
66 entity type the `subject` and the right hand entity type the |
|
67 `object` |
|
68 |
|
69 .. note:: |
|
70 |
|
71 The |yams| schema is available at run time through the .schema |
|
72 attribute of the `vregistry`. It's an instance of |
|
73 :class:`cubicweb.schema.Schema`, which extends |
|
74 :class:`yams.schema.Schema`. |
|
75 |
|
76 .. _EntityType: |
|
77 |
|
78 Entity type |
|
79 ~~~~~~~~~~~ |
|
80 |
|
81 An entity type is an instance of :class:`yams.schema.EntitySchema`. Each entity type has |
|
82 a set of attributes and relations, and some permissions which define who can add, read, |
|
83 update or delete entities of this type. |
|
84 |
|
85 The following built-in types are available: ``String``, |
|
86 ``Int``, ``BigInt``, ``Float``, ``Decimal``, ``Boolean``, |
|
87 ``Date``, ``Datetime``, ``Time``, ``Interval``, ``Byte`` and |
|
88 ``Password``. They can only be used as attributes of an other entity |
|
89 type. |
|
90 |
|
91 There is also a `RichString` kindof type: |
|
92 |
|
93 .. autoclass:: yams.buildobjs.RichString |
|
94 |
|
95 The ``__unique_together__`` class attribute is a list of tuples of names of |
|
96 attributes or inlined relations. For each tuple, CubicWeb ensures the unicity |
|
97 of the combination. For example: |
|
98 |
|
99 .. sourcecode:: python |
|
100 |
|
101 class State(EntityType): |
|
102 __unique_together__ = [('name', 'state_of')] |
|
103 |
|
104 name = String(required=True) |
|
105 state_of = SubjectRelation('Workflow', cardinality='1*', |
|
106 composite='object', inlined=True) |
|
107 |
|
108 |
|
109 You can find more base entity types in |
|
110 :ref:`pre_defined_entity_types`. |
|
111 |
|
112 .. XXX yams inheritance |
|
113 |
|
114 .. _RelationType: |
|
115 |
|
116 Relation type |
|
117 ~~~~~~~~~~~~~ |
|
118 |
|
119 A relation type is an instance of |
|
120 :class:`yams.schema.RelationSchema`. A relation type is simply a |
|
121 semantic definition of a kind of relationship that may occur in an |
|
122 application. |
|
123 |
|
124 It may be referenced by zero, one or more relation definitions. |
|
125 |
|
126 It is important to choose a good name, at least to avoid conflicts |
|
127 with some semantically different relation defined in other cubes |
|
128 (since there's only a shared name space for these names). |
|
129 |
|
130 A relation type holds the following properties (which are hence shared |
|
131 between all relation definitions of that type): |
|
132 |
|
133 * `inlined`: boolean handling the physical optimization for archiving |
|
134 the relation in the subject entity table, instead of creating a specific |
|
135 table for the relation. This applies to relations where cardinality |
|
136 of subject->relation->object is 0..1 (`?`) or 1..1 (`1`) for *all* its relation |
|
137 definitions. |
|
138 |
|
139 * `symmetric`: boolean indicating that the relation is symmetrical, which |
|
140 means that `X relation Y` implies `Y relation X`. |
|
141 |
|
142 .. _RelationDefinition: |
|
143 |
|
144 Relation definition |
|
145 ~~~~~~~~~~~~~~~~~~~ |
|
146 |
|
147 A relation definition is an instance of |
|
148 :class:`yams.schema.RelationDefinition`. It is a complete triplet |
|
149 "<subject entity type> <relation type> <object entity type>". |
|
150 |
|
151 When creating a new instance of that class, the corresponding |
|
152 :class:`RelationType` instance is created on the fly if necessary. |
|
153 |
|
154 Properties |
|
155 `````````` |
|
156 |
|
157 The available properties for relation definitions are enumerated |
|
158 here. There are several kind of properties, as some relation |
|
159 definitions are actually attribute definitions, and other are not. |
|
160 |
|
161 Some properties may be completely optional, other may have a default |
|
162 value. |
|
163 |
|
164 Common properties for attributes and relations: |
|
165 |
|
166 * `description`: a unicode string describing an attribute or a |
|
167 relation. By default this string will be used in the editing form of |
|
168 the entity, which means that it is supposed to help the end-user and |
|
169 should be flagged by the function `_` to be properly |
|
170 internationalized. |
|
171 |
|
172 * `constraints`: a list of conditions/constraints that the relation has to |
|
173 satisfy (c.f. `Constraints`_) |
|
174 |
|
175 * `cardinality`: a two character string specifying the cardinality of |
|
176 the relation. The first character defines the cardinality of the |
|
177 relation on the subject, and the second on the object. When a |
|
178 relation can have multiple subjects or objects, the cardinality |
|
179 applies to all, not on a one-to-one basis (so it must be |
|
180 consistent...). Default value is '**'. The possible values are |
|
181 inspired from regular expression syntax: |
|
182 |
|
183 * `1`: 1..1 |
|
184 * `?`: 0..1 |
|
185 * `+`: 1..n |
|
186 * `*`: 0..n |
|
187 |
|
188 Attributes properties: |
|
189 |
|
190 * `unique`: boolean indicating if the value of the attribute has to be |
|
191 unique or not within all entities of the same type (false by |
|
192 default) |
|
193 |
|
194 * `indexed`: boolean indicating if an index needs to be created for |
|
195 this attribute in the database (false by default). This is useful |
|
196 only if you know that you will have to run numerous searches on the |
|
197 value of this attribute. |
|
198 |
|
199 * `default`: default value of the attribute. In case of date types, the values |
|
200 which could be used correspond to the RQL keywords `TODAY` and `NOW`. |
|
201 |
|
202 * `metadata`: Is also accepted as an argument of the attribute contructor. It is |
|
203 not really an attribute property. see `Metadata`_ for details. |
|
204 |
|
205 Properties for `String` attributes: |
|
206 |
|
207 * `fulltextindexed`: boolean indicating if the attribute is part of |
|
208 the full text index (false by default) (*applicable on the type |
|
209 `Byte` as well*) |
|
210 |
|
211 * `internationalizable`: boolean indicating if the value of the |
|
212 attribute is internationalizable (false by default) |
|
213 |
|
214 Relation properties: |
|
215 |
|
216 * `composite`: string indicating that the subject (composite == |
|
217 'subject') is composed of the objects of the relations. For the |
|
218 opposite case (when the object is composed of the subjects of the |
|
219 relation), we just set 'object' as value. The composition implies |
|
220 that when the relation is deleted (so when the composite is deleted, |
|
221 at least), the composed are also deleted. |
|
222 |
|
223 * `fulltext_container`: string indicating if the value if the full |
|
224 text indexation of the entity on one end of the relation should be |
|
225 used to find the entity on the other end. The possible values are |
|
226 'subject' or 'object'. For instance the use_email relation has that |
|
227 property set to 'subject', since when performing a full text search |
|
228 people want to find the entity using an email address, and not the |
|
229 entity representing the email address. |
|
230 |
|
231 Constraints |
|
232 ``````````` |
|
233 |
|
234 By default, the available constraint types are: |
|
235 |
|
236 General Constraints |
|
237 ...................... |
|
238 |
|
239 * `SizeConstraint`: allows to specify a minimum and/or maximum size on |
|
240 string (generic case of `maxsize`) |
|
241 |
|
242 * `BoundaryConstraint`: allows to specify a minimum and/or maximum value |
|
243 on numeric types and date |
|
244 |
|
245 .. sourcecode:: python |
|
246 |
|
247 from yams.constraints import BoundaryConstraint, TODAY, NOW, Attribute |
|
248 |
|
249 class DatedEntity(EntityType): |
|
250 start = Date(constraints=[BoundaryConstraint('>=', TODAY())]) |
|
251 end = Date(constraints=[BoundaryConstraint('>=', Attribute('start'))]) |
|
252 |
|
253 class Before(EntityType); |
|
254 last_time = DateTime(constraints=[BoundaryConstraint('<=', NOW())]) |
|
255 |
|
256 * `IntervalBoundConstraint`: allows to specify an interval with |
|
257 included values |
|
258 |
|
259 .. sourcecode:: python |
|
260 |
|
261 class Node(EntityType): |
|
262 latitude = Float(constraints=[IntervalBoundConstraint(-90, +90)]) |
|
263 |
|
264 * `UniqueConstraint`: identical to "unique=True" |
|
265 |
|
266 * `StaticVocabularyConstraint`: identical to "vocabulary=(...)" |
|
267 |
|
268 Constraints can be dependent on a fixed value (90, Date(2015,3,23)) or a variable. |
|
269 In this second case, yams can handle : |
|
270 |
|
271 * `Attribute`: compare to the value of another attribute. |
|
272 * `TODAY`: compare to the current Date. |
|
273 * `NOW`: compare to the current Datetime. |
|
274 |
|
275 RQL Based Constraints |
|
276 ...................... |
|
277 |
|
278 RQL based constraints may take three arguments. The first one is the ``WHERE`` |
|
279 clause of a RQL query used by the constraint. The second argument ``mainvars`` |
|
280 is the ``Any`` clause of the query. By default this include `S` reserved for the |
|
281 subject of the relation and `O` for the object. Additional variables could be |
|
282 specified using ``mainvars``. The argument expects a single string with all |
|
283 variable's name separated by spaces. The last one, ``msg``, is the error message |
|
284 displayed when the constraint fails. As RQLVocabularyConstraint never fails the |
|
285 third argument is not available. |
|
286 |
|
287 * `RQLConstraint`: allows to specify a RQL query that has to be satisfied |
|
288 by the subject and/or the object of relation. In this query the variables |
|
289 `S` and `O` are reserved for the relation subject and object entities. |
|
290 |
|
291 * `RQLVocabularyConstraint`: similar to the previous type of constraint except |
|
292 that it does not express a "strong" constraint, which means it is only used to |
|
293 restrict the values listed in the drop-down menu of editing form, but it does |
|
294 not prevent another entity to be selected. |
|
295 |
|
296 * `RQLUniqueConstraint`: allows to the specify a RQL query that ensure that an |
|
297 attribute is unique in a specific context. The Query must **never** return more |
|
298 than a single result to be satisfied. In this query the variables `S` is |
|
299 reserved for the relation subject entity. The other variables should be |
|
300 specified with the second constructor argument (mainvars). This constraint type |
|
301 should be used when __unique_together__ doesn't fit. |
|
302 |
|
303 .. XXX note about how to add new constraint |
|
304 |
|
305 .. _securitymodel: |
|
306 |
|
307 The security model |
|
308 ~~~~~~~~~~~~~~~~~~ |
|
309 |
|
310 The security model of `CubicWeb` is based on `Access Control List`. |
|
311 The main principles are: |
|
312 |
|
313 * users and groups of users |
|
314 * a user belongs to at least one group of user |
|
315 * permissions (`read`, `update`, `create`, `delete`) |
|
316 * permissions are assigned to groups (and not to users) |
|
317 |
|
318 For *CubicWeb* in particular: |
|
319 |
|
320 * we associate rights at the entities/relations schema level |
|
321 |
|
322 * the default groups are: `managers`, `users` and `guests` |
|
323 |
|
324 * users belong to the `users` group |
|
325 |
|
326 * there is a virtual group called `owners` to which we can associate only |
|
327 `delete` and `update` permissions |
|
328 |
|
329 * we can not add users to the `owners` group, they are implicitly added to it |
|
330 according to the context of the objects they own |
|
331 |
|
332 * the permissions of this group are only checked on `update`/`delete` actions |
|
333 if all the other groups the user belongs to do not provide those permissions |
|
334 |
|
335 Setting permissions is done with the class attribute `__permissions__` |
|
336 of entity types and relation definitions. The value of this attribute |
|
337 is a dictionary where the keys are the access types (action), and the |
|
338 values are the authorized groups or rql expressions. |
|
339 |
|
340 For an entity type, the possible actions are `read`, `add`, `update` and |
|
341 `delete`. |
|
342 |
|
343 For a relation, the possible actions are `read`, `add`, and `delete`. |
|
344 |
|
345 For an attribute, the possible actions are `read`, `add` and `update`, |
|
346 and they are a refinement of an entity type permission. |
|
347 |
|
348 .. note:: |
|
349 |
|
350 By default, the permissions of an entity type attributes are |
|
351 equivalent to the permissions of the entity type itself. |
|
352 |
|
353 It is possible to provide custom attribute permissions which are |
|
354 stronger than, or are more lenient than the entity type |
|
355 permissions. |
|
356 |
|
357 In a situation where all attributes were given custom permissions, |
|
358 the entity type permissions would not be checked, except for the |
|
359 `delete` action. |
|
360 |
|
361 For each access type, a tuple indicates the name of the authorized groups and/or |
|
362 one or multiple RQL expressions to satisfy to grant access. The access is |
|
363 provided if the user is in one of the listed groups or if one of the RQL condition |
|
364 is satisfied. |
|
365 |
|
366 Default permissions |
|
367 ``````````````````` |
|
368 |
|
369 The default permissions for ``EntityType`` are: |
|
370 |
|
371 .. sourcecode:: python |
|
372 |
|
373 __permissions__ = { |
|
374 'read': ('managers', 'users', 'guests',), |
|
375 'update': ('managers', 'owners',), |
|
376 'delete': ('managers', 'owners'), |
|
377 'add': ('managers', 'users',) |
|
378 } |
|
379 |
|
380 The default permissions for relations are: |
|
381 |
|
382 .. sourcecode:: python |
|
383 |
|
384 __permissions__ = {'read': ('managers', 'users', 'guests',), |
|
385 'delete': ('managers', 'users'), |
|
386 'add': ('managers', 'users',)} |
|
387 |
|
388 The default permissions for attributes are: |
|
389 |
|
390 .. sourcecode:: python |
|
391 |
|
392 __permissions__ = {'read': ('managers', 'users', 'guests',), |
|
393 'add': ('managers', ERQLExpression('U has_add_permission X'), |
|
394 'update': ('managers', ERQLExpression('U has_update_permission X')),} |
|
395 |
|
396 .. note:: |
|
397 |
|
398 The default permissions for attributes are not syntactically |
|
399 equivalent to the default permissions of the entity types, but the |
|
400 rql expressions work by delegating to the entity type permissions. |
|
401 |
|
402 |
|
403 The standard user groups |
|
404 ```````````````````````` |
|
405 |
|
406 * `guests` |
|
407 |
|
408 * `users` |
|
409 |
|
410 * `managers` |
|
411 |
|
412 * `owners`: virtual group corresponding to the entity's owner. |
|
413 This can only be used for the actions `update` and `delete` of an entity |
|
414 type. |
|
415 |
|
416 It is also possible to use specific groups if they are defined in the precreate |
|
417 script of the cube (``migration/precreate.py``). Defining groups in postcreate |
|
418 script or later makes them unavailable for security purposes (in this case, an |
|
419 `sync_schema_props_perms` command has to be issued in a CubicWeb shell). |
|
420 |
|
421 |
|
422 Use of RQL expression for write permissions |
|
423 ``````````````````````````````````````````` |
|
424 |
|
425 It is possible to define RQL expression to provide update permission (`add`, |
|
426 `delete` and `update`) on entity type / relation definitions. An rql expression |
|
427 is a piece of query (corresponds to the WHERE statement of an RQL query), and the |
|
428 expression will be considered as satisfied if it returns some results. They can |
|
429 not be used in `read` permission. |
|
430 |
|
431 To use RQL expression in entity type permission: |
|
432 |
|
433 * you have to use the class :class:`~cubicweb.schema.ERQLExpression` |
|
434 |
|
435 * in this expression, the variables `X` and `U` are pre-defined references |
|
436 respectively on the current entity (on which the action is verified) and on the |
|
437 user who send the request |
|
438 |
|
439 For RQL expressions on a relation type, the principles are the same except for |
|
440 the following: |
|
441 |
|
442 * you have to use the class :class:`~cubicweb.schema.RRQLExpression` instead of |
|
443 :class:`~cubicweb.schema.ERQLExpression` |
|
444 |
|
445 * in the expression, the variables `S`, `O` and `U` are pre-defined references to |
|
446 respectively the subject and the object of the current relation (on which the |
|
447 action is being verified) and the user who executed the query |
|
448 |
|
449 To define security for attributes of an entity (non-final relation), you have to |
|
450 use the class :class:`~cubicweb.schema.ERQLExpression` in which `X` represents |
|
451 the entity the attribute belongs to. |
|
452 |
|
453 It is possible to use in those expression a special relation |
|
454 `has_<ACTION>_permission` where the subject is the user (eg 'U') and the object |
|
455 is any variable representing an entity (usually 'X' in |
|
456 :class:`~cubicweb.schema.ERQLExpression`, 'S' or 'O' in |
|
457 :class:`~cubicweb.schema.RRQLExpression`), meaning that the user needs to have |
|
458 permission to execute the action <ACTION> on the entities represented by this |
|
459 variable. It's recommanded to use this feature whenever possible since it |
|
460 simplify greatly complex security definition and upgrade. |
|
461 |
|
462 |
|
463 .. sourcecode:: python |
|
464 |
|
465 class my_relation(RelationDefinition): |
|
466 __permissions__ = {'read': ('managers', 'users'), |
|
467 'add': ('managers', RRQLExpression('U has_update_permission S')), |
|
468 'delete': ('managers', RRQLExpression('U has_update_permission S')) |
|
469 } |
|
470 |
|
471 In the above example, user will be allowed to add/delete `my_relation` if he has |
|
472 the `update` permission on the subject of the relation. |
|
473 |
|
474 .. note:: |
|
475 |
|
476 Potentially, the `use of an RQL expression to add an entity or a relation` can |
|
477 cause problems for the user interface, because if the expression uses the |
|
478 entity or the relation to create, we are not able to verify the permissions |
|
479 before we actually added the entity (please note that this is not a problem for |
|
480 the RQL server at all, because the permissions checks are done after the |
|
481 creation). In such case, the permission check methods |
|
482 (CubicWebEntitySchema.check_perm and has_perm) can indicate that the user is |
|
483 not allowed to create this entity while it would obtain the permission. To |
|
484 compensate this problem, it is usually necessary in such case to use an action |
|
485 that reflects the schema permissions but which check properly the permissions |
|
486 so that it would show up only if possible. |
|
487 |
|
488 |
|
489 Use of RQL expression for reading rights |
|
490 ```````````````````````````````````````` |
|
491 |
|
492 The principles are the same but with the following restrictions: |
|
493 |
|
494 * you can not use rql expression for the `read` permission of relations and |
|
495 attributes, |
|
496 |
|
497 * you can not use special `has_<ACTION>_permission` relation in the rql |
|
498 expression. |
|
499 |
|
500 |
|
501 Important notes about write permissions checking |
|
502 ```````````````````````````````````````````````` |
|
503 |
|
504 Write permissions (e.g. 'add', 'update', 'delete') are checked in core hooks. |
|
505 |
|
506 When a permission is checked slightly vary according to if it's an entity or |
|
507 relation, and if the relation is an attribute relation or not). It's important to |
|
508 understand that since according to when a permission is checked, values returned |
|
509 by rql expressions may changes, hence the permission being granted or not. |
|
510 |
|
511 Here are the current rules: |
|
512 |
|
513 1. permission to add/update entity and its attributes are checked on |
|
514 commit |
|
515 |
|
516 2. permission to delete an entity is checked in 'before_delete_entity' hook |
|
517 |
|
518 3. permission to add a relation is checked either: |
|
519 |
|
520 - in 'before_add_relation' hook if the relation type is in the |
|
521 `BEFORE_ADD_RELATIONS` set |
|
522 |
|
523 - else at commit time if the relation type is in the `ON_COMMIT_ADD_RELATIONS` |
|
524 set |
|
525 |
|
526 - else in 'after_add_relation' hook (the default) |
|
527 |
|
528 4. permission to delete a relation is checked in 'before_delete_relation' hook |
|
529 |
|
530 Last but not least, remember queries issued from hooks and operation are by |
|
531 default 'unsafe', eg there are no read or write security checks. |
|
532 |
|
533 See :mod:`cubicweb.hooks.security` for more details. |
|
534 |
|
535 |
|
536 .. _yams_example: |
|
537 |
|
538 |
|
539 Derived attributes and relations |
|
540 -------------------------------- |
|
541 |
|
542 .. note:: **TODO** Check organisation of the whole chapter of the documentation |
|
543 |
|
544 Cubicweb offers the possibility to *query* data using so called |
|
545 *computed* relations and attributes. Those are *seen* by RQL requests |
|
546 as normal attributes and relations but are actually derived from other |
|
547 attributes and relations. In a first section we'll informally review |
|
548 two typical use cases. Then we see how to use computed attributes and |
|
549 relations in your schema. Last we will consider various significant |
|
550 aspects of their implementation and the impact on their usage. |
|
551 |
|
552 Motivating use cases |
|
553 ~~~~~~~~~~~~~~~~~~~~ |
|
554 |
|
555 Computed (or reified) relations |
|
556 ``````````````````````````````` |
|
557 |
|
558 It often arises that one must represent a ternary relation, or a |
|
559 family of relations. For example, in the context of an exhibition |
|
560 catalog you might want to link all *contributors* to the *work* they |
|
561 contributed to, but this contribution can be as *illustrator*, |
|
562 *author*, *performer*, ... |
|
563 |
|
564 The classical way to describe this kind of information within an |
|
565 entity-relationship schema is to *reify* the relation, that is turn |
|
566 the relation into a entity. In our example the schema will have a |
|
567 *Contribution* entity type used to represent the family of the |
|
568 contribution relations. |
|
569 |
|
570 |
|
571 .. sourcecode:: python |
|
572 |
|
573 class ArtWork(EntityType): |
|
574 name = String() |
|
575 ... |
|
576 |
|
577 class Person(EntityType): |
|
578 name = String() |
|
579 ... |
|
580 |
|
581 class Contribution(EntityType): |
|
582 contributor = SubjectRelation('Person', cardinality='1*', inlined=True) |
|
583 manifestation = SubjectRelation('ArtWork') |
|
584 role = SubjectRelation('Role') |
|
585 |
|
586 class Role(EntityType): |
|
587 name = String() |
|
588 |
|
589 But then, in order to query the illustrator(s) ``I`` of a work ``W``, |
|
590 one has to write:: |
|
591 |
|
592 Any I, W WHERE C is Contribution, C contributor I, C manifestation W, |
|
593 C role R, R name 'illustrator' |
|
594 |
|
595 whereas we would like to be able to simply write:: |
|
596 |
|
597 Any I, W WHERE I illustrator_of W |
|
598 |
|
599 This is precisely what the computed relations allow. |
|
600 |
|
601 |
|
602 Computed (or synthesized) attribute |
|
603 ``````````````````````````````````` |
|
604 |
|
605 Assuming a trivial schema for describing employees in companies, one |
|
606 can be interested in the total of salaries payed by a company for |
|
607 all its employees. One has to write:: |
|
608 |
|
609 Any C, SUM(SA) GROUPBY S WHERE E works_for C, E salary SA |
|
610 |
|
611 whereas it would be most convenient to simply write:: |
|
612 |
|
613 Any C, TS WHERE C total_salary TS |
|
614 |
|
615 And this is again what computed attributes provide. |
|
616 |
|
617 |
|
618 Using computed attributes and relations |
|
619 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|
620 |
|
621 Computed (or reified) relations |
|
622 ``````````````````````````````` |
|
623 |
|
624 In the above case we would define the *computed relation* |
|
625 ``illustrator_of`` in the schema by: |
|
626 |
|
627 .. sourcecode:: python |
|
628 |
|
629 class illustrator_of(ComputedRelation): |
|
630 rule = ('C is Contribution, C contributor S, C manifestation O,' |
|
631 'C role R, R name "illustrator"') |
|
632 |
|
633 You will note that: |
|
634 |
|
635 * the ``S`` and ``O`` RQL variables implicitly identify the subject and |
|
636 object of the defined computed relation, akin to what happens in |
|
637 RRQLExpression |
|
638 * the possible subject and object entity types are inferred from the rule; |
|
639 * computed relation definitions always have empty *add* and *delete* permissions |
|
640 * *read* permissions can be defined, permissions from the relations used in the |
|
641 rewrite rule **are not considered** ; |
|
642 * nothing else may be defined on the `ComputedRelation` subclass beside |
|
643 description, permissions and rule (e.g. no cardinality, composite, etc.,). |
|
644 `BadSchemaDefinition` is raised on attempt to specify other attributes; |
|
645 * computed relations can not be used in 'SET' and 'DELETE' rql queries |
|
646 (`BadQuery` exception raised). |
|
647 |
|
648 |
|
649 NB: The fact that the *add* and *delete* permissions are *empty* even |
|
650 for managers is expected to make the automatic UI not attempt to edit |
|
651 them. |
|
652 |
|
653 Computed (or synthesized) attributes |
|
654 ```````````````````````````````````` |
|
655 |
|
656 In the above case we would define the *computed attribute* |
|
657 ``total_salary`` on the ``Company`` entity type in the schema by: |
|
658 |
|
659 .. sourcecode:: python |
|
660 |
|
661 class Company(EntityType): |
|
662 name = String() |
|
663 total_salary = Int(formula='Any SUM(SA) GROUPBY E WHERE P works_for X, E salary SA') |
|
664 |
|
665 * the ``X`` RQL variable implicitly identifies the entity holding the |
|
666 computed attribute, akin to what happens in ERQLExpression; |
|
667 * the type inferred from the formula is checked against the declared type, and |
|
668 `BadSchemaDefinition` is raised if they don't match; |
|
669 * the computed attributes always have empty *update* permissions |
|
670 * `BadSchemaDefinition` is raised on attempt to set 'update' permissions; |
|
671 * 'read' permissions can be defined, permissions regarding the formula |
|
672 **are not considered**; |
|
673 * other attribute's property (inlined, ...) can be defined as for normal attributes; |
|
674 * Similarly to computed relation, computed attribute can't be used in 'SET' and |
|
675 'DELETE' rql queries (`BadQuery` exception raised). |
|
676 |
|
677 |
|
678 API and implementation |
|
679 ~~~~~~~~~~~~~~~~~~~~~~ |
|
680 |
|
681 Representation in the data backend |
|
682 `````````````````````````````````` |
|
683 |
|
684 Computed relations have no direct representation at the SQL table |
|
685 level. Instead, each time a query is issued the query is rewritten to |
|
686 replace the computed relation by its equivalent definition and the |
|
687 resulting rewritten query is performed in the usual way. |
|
688 |
|
689 On the contrary, computed attributes are represented as a column in the |
|
690 table for their host entity type, just like normal attributes. Their |
|
691 value is kept up-to-date with respect to their defintion by a system |
|
692 of hooks (also called triggers in most RDBMS) which recomputes them |
|
693 when the relations and attributes they depend on are modified. |
|
694 |
|
695 Yams API |
|
696 ```````` |
|
697 |
|
698 When accessing the schema through the *yams API* (not when defining a |
|
699 schema in a ``schema.py`` file) the computed attributes and relations |
|
700 are represented as follows: |
|
701 |
|
702 relations |
|
703 The ``yams.RelationSchema`` class has a new ``rule`` attribute |
|
704 holding the rule as a string. If this attribute is set all others |
|
705 must not be set. |
|
706 attributes |
|
707 A new property ``formula`` is added on class |
|
708 ``yams.RelationDefinitionSchema`` alomng with a new keyword |
|
709 argument ``formula`` on the initializer. |
|
710 |
|
711 Migration |
|
712 ````````` |
|
713 |
|
714 The migrations are to be handled as summarized in the array below. |
|
715 |
|
716 +------------+---------------------------------------------------+---------------------------------------+ |
|
717 | | Computed rtype | Computed attribute | |
|
718 +============+===================================================+=======================================+ |
|
719 | add | * add_relation_type | * add_attribute | |
|
720 | | * add_relation_definition should trigger an error | * add_relation_definition | |
|
721 +------------+---------------------------------------------------+---------------------------------------+ |
|
722 | modify | * sync_schema_prop_perms: | * sync_schema_prop_perms: | |
|
723 | | checks the rule is | | |
|
724 | (rule or | synchronized with the database | - empty the cache, | |
|
725 | formula) | | - check formula, | |
|
726 | | | - make sure all the values get | |
|
727 | | | updated | |
|
728 +------------+---------------------------------------------------+---------------------------------------+ |
|
729 | del | * drop_relation_type | * drop_attribute | |
|
730 | | * drop_relation_definition should trigger an error| * drop_relation_definition | |
|
731 +------------+---------------------------------------------------+---------------------------------------+ |
|
732 |
|
733 |
|
734 Defining your schema using yams |
|
735 ------------------------------- |
|
736 |
|
737 Entity type definition |
|
738 ~~~~~~~~~~~~~~~~~~~~~~ |
|
739 |
|
740 An entity type is defined by a Python class which inherits from |
|
741 :class:`yams.buildobjs.EntityType`. The class definition contains the |
|
742 description of attributes and relations for the defined entity type. |
|
743 The class name corresponds to the entity type name. It is expected to |
|
744 be defined in the module ``mycube.schema``. |
|
745 |
|
746 :Note on schema definition: |
|
747 |
|
748 The code in ``mycube.schema`` is not meant to be executed. The class |
|
749 EntityType mentioned above is different from the EntitySchema class |
|
750 described in the previous chapter. EntityType is a helper class to |
|
751 make Entity definition easier. Yams will process EntityType classes |
|
752 and create EntitySchema instances from these class definitions. Similar |
|
753 manipulation happen for relations. |
|
754 |
|
755 When defining a schema using python files, you may use the following shortcuts: |
|
756 |
|
757 - `required`: boolean indicating if the attribute is required, ed subject cardinality is '1' |
|
758 |
|
759 - `vocabulary`: specify static possible values of an attribute |
|
760 |
|
761 - `maxsize`: integer providing the maximum size of a string (no limit by default) |
|
762 |
|
763 For example: |
|
764 |
|
765 .. sourcecode:: python |
|
766 |
|
767 class Person(EntityType): |
|
768 """A person with the properties and the relations necessary for my |
|
769 application""" |
|
770 |
|
771 last_name = String(required=True, fulltextindexed=True) |
|
772 first_name = String(required=True, fulltextindexed=True) |
|
773 title = String(vocabulary=('Mr', 'Mrs', 'Miss')) |
|
774 date_of_birth = Date() |
|
775 works_for = SubjectRelation('Company', cardinality='?*') |
|
776 |
|
777 |
|
778 The entity described above defines three attributes of type String, |
|
779 last_name, first_name and title, an attribute of type Date for the date of |
|
780 birth and a relation that connects a `Person` to another entity of type |
|
781 `Company` through the semantic `works_for`. |
|
782 |
|
783 |
|
784 |
|
785 :Naming convention: |
|
786 |
|
787 Entity class names must start with an uppercase letter. The common |
|
788 usage is to use ``CamelCase`` names. |
|
789 |
|
790 Attribute and relation names must start with a lowercase letter. The |
|
791 common usage is to use ``underscore_separated_words``. Attribute and |
|
792 relation names starting with a single underscore are permitted, to |
|
793 denote a somewhat "protected" or "private" attribute. |
|
794 |
|
795 In any case, identifiers starting with "CW" or "cw" are reserved for |
|
796 internal use by the framework. |
|
797 |
|
798 .. _Metadata: |
|
799 |
|
800 Some attribute using the name of another attribute as prefix are considered |
|
801 metadata. For example, if an EntityType have both a ``data`` and |
|
802 ``data_format`` attribute, ``data_format`` is view as the ``format`` metadata |
|
803 of ``data``. Later the :meth:`cw_attr_metadata` method will allow you to fetch |
|
804 metadata related to an attribute. There are only three valid metadata names: |
|
805 ``format``, ``encoding`` and ``name``. |
|
806 |
|
807 |
|
808 The name of the Python attribute corresponds to the name of the attribute |
|
809 or the relation in *CubicWeb* application. |
|
810 |
|
811 An attribute is defined in the schema as follows:: |
|
812 |
|
813 attr_name = AttrType(*properties, metadata={}) |
|
814 |
|
815 where |
|
816 |
|
817 * `AttrType`: is one of the type listed in EntityType_, |
|
818 |
|
819 * `properties`: is a list of the attribute needs to satisfy (see `Properties`_ |
|
820 for more details), |
|
821 |
|
822 * `metadata`: is a dictionary of meta attributes related to ``attr_name``. |
|
823 Dictionary keys are the name of the meta attribute. Dictionary values |
|
824 attributes objects (like the content of ``AttrType``). For each entry of the |
|
825 metadata dictionary a ``<attr_name>_<key> = <value>`` attribute is |
|
826 automaticaly added to the EntityType. see `Metadata`_ section for details |
|
827 about valid key. |
|
828 |
|
829 |
|
830 --- |
|
831 |
|
832 While building your schema |
|
833 |
|
834 * it is possible to use the attribute `meta` to flag an entity type as a `meta` |
|
835 (e.g. used to describe/categorize other entities) |
|
836 |
|
837 .. XXX the paragraph below needs clarification and / or moving out in |
|
838 .. another place |
|
839 |
|
840 *Note*: if you end up with an `if` in the definition of your entity, this probably |
|
841 means that you need two separate entities that implement the `ITree` interface and |
|
842 get the result from `.children()` which ever entity is concerned. |
|
843 |
|
844 .. Inheritance |
|
845 .. ``````````` |
|
846 .. XXX feed me |
|
847 |
|
848 |
|
849 Definition of relations |
|
850 ~~~~~~~~~~~~~~~~~~~~~~~ |
|
851 |
|
852 .. XXX add note about defining relation type / definition |
|
853 |
|
854 A relation is defined by a Python class heriting `RelationType`. The name |
|
855 of the class corresponds to the name of the type. The class then contains |
|
856 a description of the properties of this type of relation, and could as well |
|
857 contain a string for the subject and a string for the object. This allows to create |
|
858 new definition of associated relations, (so that the class can have the |
|
859 definition properties from the relation) for example :: |
|
860 |
|
861 class locked_by(RelationType): |
|
862 """relation on all entities indicating that they are locked""" |
|
863 inlined = True |
|
864 cardinality = '?*' |
|
865 subject = '*' |
|
866 object = 'CWUser' |
|
867 |
|
868 If provided, the `subject` and `object` attributes denote the subject |
|
869 and object of the various relation definitions related to the relation |
|
870 type. Allowed values for these attributes are: |
|
871 |
|
872 * a string corresponding to an entity type |
|
873 * a tuple of string corresponding to multiple entity types |
|
874 * the '*' special string, meaning all types of entities |
|
875 |
|
876 When a relation is not inlined and not symmetrical, and it does not require |
|
877 specific permissions, it can be defined using a `SubjectRelation` |
|
878 attribute in the EntityType class. The first argument of `SubjectRelation` gives |
|
879 the entity type for the object of the relation. |
|
880 |
|
881 :Naming convention: |
|
882 |
|
883 Although this way of defining relations uses a Python class, the |
|
884 naming convention defined earlier prevails over the PEP8 conventions |
|
885 used in the framework: relation type class names use |
|
886 ``underscore_separated_words``. |
|
887 |
|
888 :Historical note: |
|
889 |
|
890 It has been historically possible to use `ObjectRelation` which |
|
891 defines a relation in the opposite direction. This feature is |
|
892 deprecated and therefore should not be used in newly written code. |
|
893 |
|
894 :Future deprecation note: |
|
895 |
|
896 In an even more remote future, it is quite possible that the |
|
897 SubjectRelation shortcut will become deprecated, in favor of the |
|
898 RelationType declaration which offers some advantages in the context |
|
899 of reusable cubes. |
|
900 |
|
901 |
|
902 |
|
903 |
|
904 Handling schema changes |
|
905 ~~~~~~~~~~~~~~~~~~~~~~~ |
|
906 |
|
907 Also, it should be clear that to properly handle data migration, an |
|
908 instance's schema is stored in the database, so the python schema file |
|
909 used to defined it is only read when the instance is created or |
|
910 upgraded. |
|
911 |
|
912 .. XXX complete me |
|