doc/book/en/development/datamodel/definition.rst
brancholdstable
changeset 5422 0865e1e90674
parent 4985 02b52bf9f5f8
parent 5421 8167de96c523
child 5424 8ecbcbff9777
equal deleted inserted replaced
4985:02b52bf9f5f8 5422:0865e1e90674
     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)