.. -*- coding: utf-8 -*-

Définition d'un type d'entité
-----------------------------

Un type d'entité est définit par une classe python héritant de `EntityType`. Le
nom de la classe correspond au nom du type. Ensuite le corps de la classe
contient la description des attributs et des relations pour ce type d'entité,
par exemple ::

  class Personne(EntityType):
    """une personne avec les propriétés et relations nécessaires à mon
    application"""

    nom = String(required=True, fulltextindexed=True)
    prenom = String(required=True, fulltextindexed=True)
    civilite = String(vocabulary=('M', 'Mme', 'Mlle'))
    date_naiss = Date()
    travaille_pour = SubjectRelation('Company', cardinality='?*')

* le nom de l'attribut python correspond au nom de l'attribut ou de la relation
  dans cubicweb.

* tout les types de bases sont disponibles nativement : `String`, `Int`, `Float`,
  `Boolean`, `Date`, `Datetime`, `Time`, `Byte`.

* Chaque type d'entité a au moins les méta-relations suivantes :

  - `eid` (`Int`)
  
  - `creation_date` (`Datetime`)
  
  - `modification_date` (`Datetime`)
  
  - `created_by` (`CWUser`) (quel utilisateur a créé l'entité)
  
  - `owned_by` (`CWUser`) (à qui appartient l'entité, par défaut le
     créateur mais pas forcément et il peut exister plusieurs propriétaires)
     
  - `is` (`CWEType`)

  
* il est également possible de définir des relations dont le type d'entité est
  l'objet en utilisant `ObjectRelation` plutôt que `SubjectRelation`

* le premier argument de `SubjectRelation` et `ObjectRelation` donne
  respectivement le type d'entité objet /sujet de la relation. Cela
  peut être : 

  * une chaine de caractères correspondant à un type d'entité

  * un tuple de chaines de caractères correspondant à plusieurs types d'entité

  * les chaînes de caractères spéciales suivantes :

    - "**" : tout les types d'entité
    - "*" : tout les types d'entité non méta
    - "@" : tout les types d'entité méta mais non "système" (i.e. servant à la
      description du schema en base)

* il est possible d'utiliser l'attribut possible `meta` pour marquer un type
  d'entité comme étant "méta" (i.e. servant à décrire / classifier d'autre
  entités) 

* propriétés optionnelles des attributs et relations : 

  - `description` : chaine de caractères décrivant un attribut ou une
    relation. Par défaut cette chaine sera utilisée dans le formulaire de saisie
    de l'entité, elle est donc destinée à aider l'utilisateur final et doit être
    marquée par la fonction `_` pour être correctement internationalisée.

  - `constraints` : liste de contraintes devant être respecté par la relation
    (c.f. `Contraintes`_)

  - `cardinality` : chaine de 2 caractères spécifiant la cardinalité de la
    relation. Le premier caractère donne la cardinalité de la relation sur le
    sujet, le 2eme sur l'objet. Quand une relation possède plusieurs sujets ou
    objets possibles, la cardinalité s'applique sur l'ensemble et non un à un (et
    doit donc à priori être cohérente...). Les valeurs possibles sont inspirées
    des expressions régulières :

    * `1`: 1..1
    * `?`: 0..1
    * `+`: 1..n
    * `*`: 0..n

  - `meta` : booléen indiquant que la relation est une méta relation (faux par
    défaut)

* propriétés optionnelles des attributs : 

  - `required` : booléen indiquant si l'attribut est obligatoire (faux par
    défaut)

  - `unique` : booléen indiquant si la valeur de l'attribut doit être unique
    parmi toutes les entités de ce type (faux par défaut)

  - `indexed` : booléen indiquant si un index doit être créé dans la base de
    données sur cette attribut (faux par défaut). C'est utile uniquement si vous
    savez que vous allez faire de nombreuses recherche sur la valeur de cet
    attribut. 

  - `default` : valeur par défaut de l'attribut. A noter que dans le cas des
    types date, les chaines de caractères correspondant aux mots-clés RQL
    `TODAY` et `NOW` sont utilisables.

  - `vocabulary` : spécifie statiquement les valeurs possibles d'un attribut

* propriétés optionnelles des attributs de type `String` : 

  - `fulltextindexed` : booléen indiquant si l'attribut participe à l'index plein
    texte (faux par défaut) (*valable également sur le type `Byte`*)

  - `internationalizable` : booléen indiquant si la valeur de cet attribut est
    internationalisable (faux par défaut) 

  - `maxsize` : entier donnant la taille maximum de la chaine (pas de limite par
    défaut)  

* propriétés optionnelles des relations : 

  - `composite` : chaîne indiquant que le sujet (composite == 'subject') est
    composé de ou des objets de la relation. Pour le cas opposé (l'objet est
    composé de ou des sujets de la relation, il suffit de mettre 'object' comme
    valeur. La composition implique que quand la relation est supprimé (et donc
    aussi quand le composite est supprimé), le ou les composés le sont
    également. 

Contraintes
```````````
Par défaut les types de contraintes suivant sont disponibles :

* `SizeConstraint` : permet de spécifier une taille minimale et/ou maximale sur
  les chaines de caractères (cas générique de `maxsize`)

* `BoundConstraint` : permet de spécifier une valeur minimale et/ou maximale sur
  les types numériques

* `UniqueConstraint` : identique à "unique=True"

* `StaticVocabularyConstraint` : identique à "vocabulary=(...)"

* `RQLConstraint` : permet de spécifier une requête RQL devant être satisfaite
  par le sujet et/ou l'objet de la relation. Dans cette requête les variables `S`
  et `O` sont préféfinies respectivement comme l'entité sujet et objet de la
  relation

* `RQLVocabularyConstraint` : similaire à la précédente, mais exprimant une
  contrainte "faible", i.e. servant uniquement à limiter les valeurs apparaissant
  dans la liste déroulantes du formulaire d'édition, mais n'empêchant pas une
  autre entité d'être séléctionnée


Définition d'un type de relation
--------------------------------

Un type de relation est définit par une classe python héritant de `RelationType`. Le
nom de la classe correspond au nom du type. Ensuite le corps de la classe
contient la description des propriétés de ce type de relation, ainsi
qu'éventuellement une chaine pour le sujet et une autre pour l'objet permettant
de créer des définitions de relations associées (auquel cas il est possibles de
donner sur la classe les propriétés de définition de relation explicitées
ci-dessus), par exemple ::

  class verrouille_par(RelationType):
    """relation sur toutes les entités applicatives indiquant que celles-ci sont vérouillées
    inlined = True
    cardinality = '?*'
    subject = '*'
    object = 'CWUser'

En plus des permissions, les propriétés propres aux types de relation (et donc
partagés par toutes les définitions de relation de ce type) sont :

* `inlined` : booléen contrôlant l'optimisation physique consistant à stocker la
  relation dans la table de l'entité sujet au lieu de créer une table spécifique
  à la relation. Cela se limite donc aux relations dont la cardinalité
  sujet->relation->objet vaut 0..1 ('?') ou 1..1 ('1')

* `symetric` : booléen indiquant que la relation est symétrique. i.e.
  `X relation Y` implique `Y relation X`

Dans le cas de définitions de relations simultanée, `sujet` et `object` peuvent
tout deux valoir la même chose que décrite pour le 1er argument de
`SubjectRelation` et `ObjectRelation`.

A partir du moment où une relation n'est ni mise en ligne, ni symétrique, et
ne nécessite pas de permissions particulières, sa définition (en utilisant
`SubjectRelation` ou `ObjectRelation`) est suffisante.


Définition des permissions
--------------------------

La définition des permissions se fait à l'aide de l'attribut `permissions` des
types d'entité ou de relation. Celui-ci est un dictionnaire dont les clés sont
les types d'accès (action), et les valeurs les groupes ou expressions autorisées. 

Pour un type d'entité, les actions possibles sont `read`, `add`, `update` et
`delete`.

Pour un type de relation, les actions possibles sont `read`, `add`, et `delete`.

Pour chaque type d'accès, un tuple indique le nom des groupes autorisés et/ou
une ou plusieurs expressions RQL devant être vérifiées pour obtenir
l'accès. L'accès est donné à partir du moment où l'utilisateur fait parti d'un
des groupes requis ou dès qu'une expression RQL est vérifiée.

Les groupes standards sont :

* `guests`

* `users`

* `managers`

* `owners` : groupe virtuel correspondant au propriétaire d'une entité. Celui-ci
  ne peut être utilisé que pour les actions `update` et `delete` d'un type
  d'entité. 

Il est également possible d'utiliser des groupes spécifiques devant être pour
cela créés dans le precreate de l'application (`migration/precreate.py`).

Utilisation d'expression RQL sur les droits en écriture
```````````````````````````````````````````````````````
Il est possible de définir des expressions RQL donnant des droits de
modification (`add`, `delete`, `update`) sur les types d'entité et de relation.

Expression RQL pour les permissions sur un type d'entité :

* il faut utiliser la classe `ERQLExpression`

* l'expression utilisée correspond à la clause WHERE d'une requête RQL

* dans cette expression, les variables X et U sont des références prédéfinies
  respectivement sur l'entité courante (sur laquelle l'action est vérifiée) et
  sur l'utilisateur ayant effectué la requête

* il est possible d'utiliser dans cette expression les relations spéciales
  "has_<ACTION>_permission" dont le sujet est l'utilisateur et l'objet une
  variable quelquonque, signifiant ainsi que l'utilisateur doit avoir la
  permission d'effectuer l'action <ACTION> sur la ou les entités liées cette
  variable

Pour les expressions RQL sur un type de relation, les principes sont les mêmes
avec les différences suivantes :

* il faut utiliser la classe `RRQLExpression` dans le cas d'une relation non
  finale

* dans cette expression, les variables S, O et U sont des références
  prédéfinies respectivement sur le sujet et l'objet de la relation
  courante (sur laquelle l'action est vérifiée) et sur l'utilisateur
  ayant effectué la requête

* On peut aussi définir des droits sur les attributs d'une entité (relation non
  finale), sachant les points suivants :

  - pour définir des expressions rql, il faut utiliser la classe `ERQLExpression`
    dans laquelle X représentera l'entité auquel appartient l'attribut

  - les permissions 'add' et 'delete' sont équivalentes. En pratique seul
    'add'/'read' son pris en considération


En plus de cela, le type d'entité `CWPermission` de la librairie standard permet
de construire des modèles de sécurités très complexes et dynamiques. Le schéma
de ce type d'entité est le suivant : ::


    class CWPermission(MetaEntityType):
	"""entity type that may be used to construct some advanced security configuration
	"""
	name = String(required=True, indexed=True, internationalizable=True, maxsize=100)
	require_group = SubjectRelation('CWGroup', cardinality='+*',
					description=_('groups to which the permission is granted'))
	require_state = SubjectRelation('State',
				    description=_("entity'state in which the permission is applyable"))
	# can be used on any entity
	require_permission = ObjectRelation('**', cardinality='*1', composite='subject',
					    description=_("link a permission to the entity. This "
							  "permission should be used in the security "
							  "definition of the entity's type to be useful."))


Exemple de configuration extrait de *jpl* ::

    ...

    class Version(EntityType):
	"""a version is defining the content of a particular project's release"""

	permissions = {'read':   ('managers', 'users', 'guests',),
		       'update': ('managers', 'logilab', 'owners',),
		       'delete': ('managers', ),
		       'add':    ('managers', 'logilab',
				  ERQLExpression('X version_of PROJ, U in_group G,'
						 'PROJ require_permission P, P name "add_version",'
						 'P require_group G'),)}

    ...

    class version_of(RelationType):
	"""link a version to its project. A version is necessarily linked to one and only one project.
	"""
	permissions = {'read':   ('managers', 'users', 'guests',),
		       'delete': ('managers', ),
		       'add':    ('managers', 'logilab',
				  RRQLExpression('O require_permission P, P name "add_version",'
						 'U in_group G, P require_group G'),)
		       }
	inlined = True

Cette configuration suppose indique qu'une entité `CWPermission` de nom
"add_version" peut-être associée à un projet et donner le droit de créer des
versions sur ce projet à des groupes spécifiques. Il est important de noter les
points suivants :

* dans ce cas il faut protéger à la fois le type d'entité "Version" et la
  relation liant une version à un projet ("version_of")

* du fait de la généricité du type d'entité `CWPermission`, il faut effectuer
  l'unification avec les groupes et / ou les états le cas échéant dans
  l'expression ("U in_group G, P require_group G" dans l'exemple ci-dessus)


Utilisation d'expression RQL sur les droits en lecture
``````````````````````````````````````````````````````
Les principes sont les mêmes mais avec les restrictions suivantes :

* on ne peut de `RRQLExpression` sur les types de relation en lecture

* les relations spéciales "has_<ACTION>_permission" ne sont pas utilisables


Note sur l'utilisation d'expression RQL sur la permission 'add'
```````````````````````````````````````````````````````````````
L'utilisation d'expression RQL sur l'ajout d'entité ou de relation pose
potentiellement un problème pour l'interface utilisateur car si l'expression
utilise l'entité ou la relation à créer, on est pas capable de vérifier les
droits avant d'avoir effectué l'ajout (noter que cela n'est pas un problème coté
serveur rql car la vérification des droits est effectuée après l'ajout
effectif). Dans ce cas les méthodes de vérification des droits (check_perm,
has_perm) peuvent inidquer qu'un utilisateur n'a pas le droit d'ajout alors
qu'il pourrait effectivement l'obtenir. Pour palier à ce soucis il est en général
nécessaire dans tel cas d'utiliser une action reflétant les droits du schéma
mais permettant de faire la vérification correctement afin qu'elle apparaisse
bien le cas échéant.

Mise à jour du schema
`````````````````````

Il faut ensuite lancer son cubicweb en mode shell ::

   cubicweb-ctl shell moninstance

Et taper ::

   add_entity_type('Personne')

Et on relance l'application!
