1 .. -*- coding: utf-8 -*- |
|
2 |
|
3 Yams *schema* |
|
4 ------------- |
|
5 |
|
6 The **schema** is the core piece of a *CubicWeb* instance as it defines |
|
7 the handled data model. It is based on entity types that are either already |
|
8 defined in the *CubicWeb* standard library; or more specific types defined |
|
9 in cubes. The schema for a cube is defined in a :file:schema.py file or in |
|
10 one or more Python files under the :file:`schema` directory (python package). |
|
11 |
|
12 At this point, it is important to make clear the difference between |
|
13 *relation type* and *relation definition*: a *relation type* is only a relation |
|
14 name with potentially other additionnal properties (see below), whereas a |
|
15 *relation definition* is a complete triplet |
|
16 "<subject entity type> <relation type> <object entity type>". |
|
17 A relation type could have been implied if none is related to a |
|
18 relation definition of the schema. |
|
19 |
|
20 Also, it should be clear that to properly handle data migration, an instance'schema |
|
21 is stored in the database, so the python schema file used to defined it are only readen |
|
22 when the instance is created or upgraded. |
|
23 |
|
24 The following built-in types are available : `String`, `Int`, `Float`, |
|
25 `Decimal`, `Boolean`, `Date`, `Datetime`, `Time`, `Interval`, `Byte` |
|
26 and `Password`. |
|
27 |
|
28 You'll also have access to :ref:`base cubicweb entity types <CWBaseEntityTypes>`. |
|
29 |
|
30 The instance schema is accessible through the .schema attribute of the |
|
31 `vregistry`. It's an instance of :class:`cubicweb.schema.Schema`, which |
|
32 extends :class:`yams.schema.Schema`. |
|
33 |
|
34 :note: |
|
35 In previous yams versions, almost all classes where available without |
|
36 any import, but the should now be explicitely imported. |
|
37 |
|
38 |
|
39 Entity type |
|
40 ~~~~~~~~~~~ |
|
41 It's an instance of :class:`yams.schema.EntitySchema`. Each entity types has |
|
42 a set of attributes and relation and some permissions, defining who can add, read, |
|
43 update or delete entities of this type. |
|
44 |
|
45 XXX yams inheritance |
|
46 |
|
47 Relation type |
|
48 ~~~~~~~~~~~~~ |
|
49 It's an instance of :class:`yams.schema.RelationSchema`. A relation type is simply |
|
50 a semantic definition of a kind of relationship that may occurs in your application. |
|
51 |
|
52 It's important to choose a good name, at least to avoid conflicts with some semantically |
|
53 different relation defined in other cubes (since we've no namespace yet). |
|
54 |
|
55 A relation type hold the following properties (which are hence shared between all |
|
56 relation definitions of that type): |
|
57 |
|
58 * `inlined` : boolean handling the physical optimization for archiving |
|
59 the relation in the subject entity table, instead of creating a specific |
|
60 table for the relation. This applies to relations where cardinality |
|
61 of subject->relation->object is 0..1 (`?`) or 1..1 (`1`) for *all* its relation |
|
62 definitions. |
|
63 |
|
64 * `symmetric` : boolean indicating that the relation is symmetrical, which |
|
65 means that `X relation Y` implies `Y relation X`. |
|
66 |
|
67 |
|
68 Relation definition |
|
69 ~~~~~~~~~~~~~~~~~~~ |
|
70 It's an instance of :class:`yams.schema.RelationDefinition`. It is a complete triplet |
|
71 "<subject entity type> <relation type> <object entity type>". |
|
72 |
|
73 Properties |
|
74 `````````` |
|
75 |
|
76 * Optional properties for attributes and relations : |
|
77 |
|
78 - `description` : a string describing an attribute or a relation. By default |
|
79 this string will be used in the editing form of the entity, which means |
|
80 that it is supposed to help the end-user and should be flagged by the |
|
81 function `_` to be properly internationalized. |
|
82 |
|
83 - `constraints` : a list of conditions/constraints that the relation has to |
|
84 satisfy (c.f. `Constraints`_) |
|
85 |
|
86 - `cardinality` : a two character string which specify the cardinality of the |
|
87 relation. The first character defines the cardinality of the relation on |
|
88 the subject, and the second on the object. When a relation can have |
|
89 multiple subjects or objects, the cardinality applies to all, |
|
90 not on a one-to-one basis (so it must be consistent...). The possible |
|
91 values are inspired from regular expression syntax : |
|
92 |
|
93 * `1`: 1..1 |
|
94 * `?`: 0..1 |
|
95 * `+`: 1..n |
|
96 * `*`: 0..n |
|
97 |
|
98 * optional properties for attributes : |
|
99 |
|
100 - `unique` : boolean indicating if the value of the attribute has to be unique |
|
101 or not within all entities of the same type (false by default) |
|
102 |
|
103 - `indexed` : boolean indicating if an index needs to be created for this |
|
104 attribute in the database (false by default). This is useful only if |
|
105 you know that you will have to run numerous searches on the value of this |
|
106 attribute. |
|
107 |
|
108 - `default` : default value of the attribute. In case of date types, the values |
|
109 which could be used correspond to the RQL keywords `TODAY` and `NOW`. |
|
110 |
|
111 * optional properties of type `String` : |
|
112 |
|
113 - `fulltextindexed` : boolean indicating if the attribute is part of |
|
114 the full text index (false by default) (*applicable on the type `Byte` |
|
115 as well*) |
|
116 |
|
117 - `internationalizable` : boolean indicating if the value of the attribute |
|
118 is internationalizable (false by default) |
|
119 |
|
120 * optional properties for relations : |
|
121 |
|
122 - `composite` : string indicating that the subject (composite == 'subject') |
|
123 is composed of the objects of the relations. For the opposite case (when |
|
124 the object is composed of the subjects of the relation), we just set |
|
125 'object' as value. The composition implies that when the relation |
|
126 is deleted (so when the composite is deleted, at least), the composed are also deleted. |
|
127 |
|
128 - `fti_container`: XXX feed me |
|
129 |
|
130 Constraints |
|
131 ``````````` |
|
132 |
|
133 By default, the available constraint types are : |
|
134 |
|
135 General Constraints |
|
136 ...................... |
|
137 |
|
138 * `SizeConstraint` : allows to specify a minimum and/or maximum size on |
|
139 string (generic case of `maxsize`) |
|
140 |
|
141 * `BoundConstraint` : allows to specify a minimum and/or maximum value on |
|
142 numeric types |
|
143 |
|
144 * `UniqueConstraint` : identical to "unique=True" |
|
145 |
|
146 * `StaticVocabularyConstraint` : identical to "vocabulary=(...)" |
|
147 |
|
148 XXX Attribute, TODAY, NOW |
|
149 |
|
150 RQL Based Constraints |
|
151 ...................... |
|
152 |
|
153 RQL based constraints may take three arguments. The first one is the ``WHERE`` |
|
154 clause of a RQL query used by the constraint. The second argument ``mainvars`` |
|
155 is the ``Any`` clause of the query. By default this include `S` reserved for the |
|
156 subject of the relation and `O` for the object. Additional variables could be |
|
157 specified using ``mainvars``. The argument expects a single string with all |
|
158 variable's name separated by spaces. The last one, ``msg``, is the error message |
|
159 displayed when the constraint fails. As RQLVocabularyConstraint never fails the |
|
160 third argument is not available. |
|
161 |
|
162 * `RQLConstraint` : allows to specify a RQL query that has to be satisfied |
|
163 by the subject and/or the object of relation. In this query the variables |
|
164 `S` and `O` are reserved for the entities subject and object of the |
|
165 relation. |
|
166 |
|
167 * `RQLVocabularyConstraint` : similar to the previous type of constraint except |
|
168 that it does not express a "strong" constraint, which means it is only used to |
|
169 restrict the values listed in the drop-down menu of editing form, but it does |
|
170 not prevent another entity to be selected. |
|
171 |
|
172 * `RQLUniqueConstraint` : allows to the specify a RQL query that ensure that an |
|
173 attribute is unique in a specific context. The Query must **never** return more |
|
174 than a single result to be satisfied. In this query the variables `S` is |
|
175 reserved for the entity subject of the relation. The other variable should be |
|
176 specified with the second constructor argument (mainvars). This constraints |
|
177 should be used when UniqueConstraint doesn't fit. Here is a simple example :: |
|
178 |
|
179 # Check that in the same Workflow each state's name is unique. Using |
|
180 # UniqueConstraint (or unique=True) here would prevent states in different |
|
181 # workflows to have the same name. |
|
182 |
|
183 # With: State S, Workflow W, String N ; S state_of W, S name N |
|
184 |
|
185 RQLUniqueConstraint('S name N, S state_of WF, Y state_of WF, Y name N', |
|
186 mainvars='Y', |
|
187 msg=_('workflow already have a state of that name')) |
|
188 |
|
189 |
|
190 |
|
191 * `RQLUniqueConstraint` : allows to the specify a RQL query that ensure that an |
|
192 attribute is unique in a specific context. The Query must **never** return more |
|
193 than a single result to be satisfied. In this query the variables `S` is |
|
194 reserved for the entity subject of the relation. The other variable should be |
|
195 specified with the second constructor argument (mainvars). This constraints |
|
196 should be used when UniqueConstraint doesn't fit. Here is a simple example :: |
|
197 |
|
198 # Check that in the same Workflow each state's name is unique. Using |
|
199 # UniqueConstraint (or unique=True) here would prevent states in different |
|
200 # workflows to have the same name. |
|
201 |
|
202 # With: State S, Workflow W, String N ; S state_of W, S name N |
|
203 |
|
204 RQLUniqueConstraint('S name N, S state_of WF, Y state_of WF, Y name N', |
|
205 mainvars='Y', |
|
206 msg=_('workflow already have a state of that name')) |
|
207 |
|
208 |
|
209 |
|
210 XXX note about how to add new constraint |
|
211 |
|
212 .. _securitymodel: |
|
213 |
|
214 |
|
215 The security model |
|
216 ~~~~~~~~~~~~~~~~~~ |
|
217 |
|
218 The security model of `cubicWeb` is based on `Access Control List`. |
|
219 The main principles are: |
|
220 |
|
221 * users and groups of users |
|
222 * a user belongs to at least one group of user |
|
223 * permissions (read, update, create, delete) |
|
224 * permissions are assigned to groups (and not to users) |
|
225 |
|
226 For *CubicWeb* in particular: |
|
227 |
|
228 * we associate rights at the enttities/relations schema level |
|
229 * for each entity, we distinguish four kind of permissions: read, |
|
230 add, update and delete |
|
231 * for each relation, we distinguish three kinds of permissions: read, |
|
232 add and delete (we can not modify a relation) |
|
233 * the basic groups are: Administrators, Users and Guests |
|
234 * by default, users belong to the group Users |
|
235 * there is a virtual group called `Owners` to which we |
|
236 can associate only deletion and update permissions |
|
237 * we can not add users to the `Owners` group, they are |
|
238 implicitly added to it according to the context of the objects |
|
239 they own |
|
240 * the permissions of this group are only checked on update/deletion |
|
241 actions if all the other groups the user belongs to does not provide |
|
242 those permissions |
|
243 |
|
244 Setting permissions is done with the attribute `__permissions__` of entities and |
|
245 relation types. It defines a dictionary where the keys are the access types |
|
246 (action), and the values are the authorized groups or expressions. |
|
247 |
|
248 For an entity type, the possible actions are `read`, `add`, `update` and |
|
249 `delete`. |
|
250 |
|
251 For a relation type, the possible actions are `read`, `add`, and `delete`. |
|
252 |
|
253 For each access type, a tuple indicates the name of the authorized groups and/or |
|
254 one or multiple RQL expressions to satisfy to grant access. The access is |
|
255 provided if the user is in one of the listed groups or one of if the RQL condition |
|
256 is satisfied. |
|
257 |
|
258 The standard user groups |
|
259 ```````````````````````` |
|
260 |
|
261 * `guests` |
|
262 |
|
263 * `users` |
|
264 |
|
265 * `managers` |
|
266 |
|
267 * `owners` : virtual group corresponding to the entity's owner. |
|
268 This can only be used for the actions `update` and `delete` of an entity |
|
269 type. |
|
270 |
|
271 It is also possible to use specific groups if they are defined in the |
|
272 precreate of the cube (``migration/precreate.py``). Defining groups in |
|
273 postcreate or even later makes them NOT available for security |
|
274 purposes (in this case, an `sync_schema_props_perms` command have to |
|
275 be issued in a CubicWeb shell). |
|
276 |
|
277 |
|
278 Use of RQL expression for write permissions |
|
279 ``````````````````````````````````````````` |
|
280 It is possible to define RQL expression to provide update permission |
|
281 (`add`, `delete` and `update`) on relation and entity types. |
|
282 |
|
283 RQL expression for entity type permission : |
|
284 |
|
285 * you have to use the class `ERQLExpression` |
|
286 |
|
287 * the used expression corresponds to the WHERE statement of an RQL query |
|
288 |
|
289 * in this expression, the variables X and U are pre-defined references |
|
290 respectively on the current entity (on which the action is verified) and |
|
291 on the user who send the request |
|
292 |
|
293 * it is possible to use, in this expression, a special relation |
|
294 "has_<ACTION>_permission" where the subject is the user and the |
|
295 object is any variable, meaning that the user needs to have |
|
296 permission to execute the action <ACTION> on the entities related |
|
297 to this variable |
|
298 |
|
299 For RQL expressions on a relation type, the principles are the same except |
|
300 for the following : |
|
301 |
|
302 * you have to use the class `RRQLExpression` in the case of a non-final relation |
|
303 |
|
304 * in the expression, the variables S, O and U are pre-defined references |
|
305 to respectively the subject and the object of the current relation (on |
|
306 which the action is being verified) and the user who executed the query |
|
307 |
|
308 * we can also define rights over attributes of an entity (non-final relation), |
|
309 knowing that : |
|
310 |
|
311 - to define RQL expression, we have to use the class `ERQLExpression` |
|
312 in which X represents the entity the attribute belongs to |
|
313 |
|
314 - the permissions `add` and `delete` are equivalent. Only `add`/`read` |
|
315 are actually taken in consideration. |
|
316 |
|
317 :Note on the use of RQL expression for `add` permission: |
|
318 |
|
319 Potentially, the use of an RQL expression to add an entity or a |
|
320 relation can cause problems for the user interface, because if the |
|
321 expression uses the entity or the relation to create, then we are |
|
322 not able to verify the permissions before we actually add the entity |
|
323 (please note that this is not a problem for the RQL server at all, |
|
324 because the permissions checks are done after the creation). In such |
|
325 case, the permission check methods (CubicWebEntitySchema.check_perm |
|
326 and has_perm) can indicate that the user is not allowed to create |
|
327 this entity but can obtain the permission. |
|
328 To compensate this problem, it is usually necessary, for such case, |
|
329 to use an action that reflects the schema permissions but which enables |
|
330 to check properly the permissions so that it would show up if necessary. |
|
331 |
|
332 |
|
333 Use of RQL expression for reading rights |
|
334 ```````````````````````````````````````` |
|
335 |
|
336 The principles are the same but with the following restrictions : |
|
337 |
|
338 * we can not use `RRQLExpression` on relation types for reading |
|
339 |
|
340 * special relations "has_<ACTION>_permission" can not be used |
|
341 |
|
342 |
|
343 |
|
344 |
|
345 Defining your schema using yams |
|
346 ------------------------------- |
|
347 |
|
348 Entity type definition |
|
349 ~~~~~~~~~~~~~~~~~~~~~~ |
|
350 |
|
351 An entity type is defined by a Python class which inherits from `EntityType`. |
|
352 The class definition contains the description of attributes and relations |
|
353 for the defined entity type. |
|
354 The class name corresponds to the entity type name. It is exepected to be |
|
355 defined in the module ``mycube.schema``. |
|
356 |
|
357 When defining a schema using python files, you may use the following shortcuts: |
|
358 |
|
359 - `required` : boolean indicating if the attribute is required, eg subject cardinality is '1' |
|
360 |
|
361 - `vocabulary` : specify static possible values of an attribute |
|
362 |
|
363 - `maxsize` : integer providing the maximum size of a string (no limit by default) |
|
364 |
|
365 For example: |
|
366 |
|
367 .. sourcecode:: python |
|
368 |
|
369 class Person(EntityType): |
|
370 """A person with the properties and the relations necessary for my |
|
371 application""" |
|
372 |
|
373 last_name = String(required=True, fulltextindexed=True) |
|
374 first_name = String(required=True, fulltextindexed=True) |
|
375 title = String(vocabulary=('Mr', 'Mrs', 'Miss')) |
|
376 date_of_birth = Date() |
|
377 works_for = SubjectRelation('Company', cardinality='?*') |
|
378 |
|
379 |
|
380 The entity described above defines three attributes of type String, |
|
381 last_name, first_name and title, an attribute of type Date for the date of |
|
382 birth and a relation that connects a `Person` to another entity of type |
|
383 `Company` through the semantic `works_for`. |
|
384 |
|
385 The name of the Python attribute corresponds to the name of the attribute |
|
386 or the relation in *CubicWeb* application. |
|
387 |
|
388 An attribute is defined in the schema as follows:: |
|
389 |
|
390 attr_name = attr_type(properties) |
|
391 |
|
392 where `attr_type` is one of the type listed above and `properties` is |
|
393 a list of the attribute needs to statisfy (see `Properties`_ |
|
394 for more details). |
|
395 |
|
396 |
|
397 * relations can be defined by using `ObjectRelation` or `SubjectRelation`. |
|
398 The first argument of `SubjectRelation` or `ObjectRelation` gives respectively |
|
399 the object/subject entity type of the relation. This could be : |
|
400 |
|
401 * a string corresponding to an entity type |
|
402 |
|
403 * a tuple of string corresponding to multiple entity types |
|
404 |
|
405 * special string such as follows : |
|
406 |
|
407 - "**" : all types of entities |
|
408 - "*" : all types of non-meta entities |
|
409 - "@" : all types of meta entities but not system entities (e.g. used for |
|
410 the basic schema description) |
|
411 |
|
412 * it is possible to use the attribute `meta` to flag an entity type as a `meta` |
|
413 (e.g. used to describe/categorize other entities) |
|
414 |
|
415 *Note* : if you end up with an `if` in the definition of your entity, this probably |
|
416 means that you need two separate entities that implement the `ITree` interface and |
|
417 get the result from `.children()` which ever entity is concerned. |
|
418 |
|
419 Inheritance |
|
420 ``````````` |
|
421 XXX feed me |
|
422 |
|
423 |
|
424 Definition of relations |
|
425 ~~~~~~~~~~~~~~~~~~~~~~~ |
|
426 |
|
427 XXX add note about defining relation type / definition |
|
428 |
|
429 A relation is defined by a Python class heriting `RelationType`. The name |
|
430 of the class corresponds to the name of the type. The class then contains |
|
431 a description of the properties of this type of relation, and could as well |
|
432 contain a string for the subject and a string for the object. This allows to create |
|
433 new definition of associated relations, (so that the class can have the |
|
434 definition properties from the relation) for example :: |
|
435 |
|
436 class locked_by(RelationType): |
|
437 """relation on all entities indicating that they are locked""" |
|
438 inlined = True |
|
439 cardinality = '?*' |
|
440 subject = '*' |
|
441 object = 'CWUser' |
|
442 |
|
443 In the case of simultaneous relations definitions, `subject` and `object` |
|
444 can both be equal to the value of the first argument of `SubjectRelation` |
|
445 and `ObjectRelation`. |
|
446 |
|
447 When a relation is not inlined and not symmetrical, and it does not require |
|
448 specific permissions, its definition (by using `SubjectRelation` and |
|
449 `ObjectRelation`) is all we need. |
|
450 |
|
451 |
|
452 Definition of permissions |
|
453 ~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|
454 The entity type `CWPermission` from the standard library |
|
455 allows to build very complex and dynamic security architectures. The schema of |
|
456 this entity type is as follow : |
|
457 |
|
458 .. sourcecode:: python |
|
459 |
|
460 class CWPermission(EntityType): |
|
461 """entity type that may be used to construct some advanced security configuration |
|
462 """ |
|
463 name = String(required=True, indexed=True, internationalizable=True, maxsize=100) |
|
464 require_group = SubjectRelation('CWGroup', cardinality='+*', |
|
465 description=_('groups to which the permission is granted')) |
|
466 require_state = SubjectRelation('State', |
|
467 description=_("entity's state in which the permission is applicable")) |
|
468 # can be used on any entity |
|
469 require_permission = ObjectRelation('**', cardinality='*1', composite='subject', |
|
470 description=_("link a permission to the entity. This " |
|
471 "permission should be used in the security " |
|
472 "definition of the entity's type to be useful.")) |
|
473 |
|
474 |
|
475 Example of configuration: |
|
476 |
|
477 .. sourcecode:: python |
|
478 |
|
479 class Version(EntityType): |
|
480 """a version is defining the content of a particular project's release""" |
|
481 |
|
482 __permissions__ = {'read': ('managers', 'users', 'guests',), |
|
483 'update': ('managers', 'logilab', 'owners',), |
|
484 'delete': ('managers', ), |
|
485 'add': ('managers', 'logilab', |
|
486 ERQLExpression('X version_of PROJ, U in_group G,' |
|
487 'PROJ require_permission P, P name "add_version",' |
|
488 'P require_group G'),)} |
|
489 |
|
490 |
|
491 class version_of(RelationType): |
|
492 """link a version to its project. A version is necessarily linked to one and only one project. |
|
493 """ |
|
494 __permissions__ = {'read': ('managers', 'users', 'guests',), |
|
495 'delete': ('managers', ), |
|
496 'add': ('managers', 'logilab', |
|
497 RRQLExpression('O require_permission P, P name "add_version",' |
|
498 'U in_group G, P require_group G'),) |
|
499 } |
|
500 inlined = True |
|
501 |
|
502 |
|
503 This configuration indicates that an entity `CWPermission` named |
|
504 "add_version" can be associated to a project and provides rights to create |
|
505 new versions on this project to specific groups. It is important to notice that : |
|
506 |
|
507 * in such case, we have to protect both the entity type "Version" and the relation |
|
508 associating a version to a project ("version_of") |
|
509 |
|
510 * because of the genericity of the entity type `CWPermission`, we have to execute |
|
511 a unification with the groups and/or the states if necessary in the expression |
|
512 ("U in_group G, P require_group G" in the above example) |
|