Documentation Frappe Framework

Frappe Framework

DocField virtuel

Un DocField virtuel est un attribut calculé d’un document qui n’est pas stocké en base. Cette page explique comment le définir, lui donner une source et son impact sur les API.

5 min de lectureMis à jour le Source : docs.frappe.io

Qu’est-ce qu’un DocField virtuel

Un DocField virtuel (Virtual DocField) est un attribut dynamique d’un document (ou enregistrement). C’est une valeur calculée qui n’est pas stockée dans la base de données du site. On l’utilise pour représenter des valeurs qui dépendent d’autres attributs statiques du document.

Prenons l’exemple d’un DocType « Person ». L’âge d’une personne se déduit de sa date de naissance : si vous connaissez la date de naissance, vous pouvez calculer l’âge. L’âge est en outre une valeur continue qui change chaque année, chaque mois, chaque jour, voire chaque heure selon la granularité souhaitée. Autre exemple : le nom complet. La plupart des implémentations stockent un prénom, un deuxième prénom et un nom de famille, et les affichent concaténés, comme « Jon Raphael Doe ». Enregistrer le nom complet comme attribut séparé n’a guère de sens puisque les chaînes se concatènent facilement. Ce sont deux cas typiques où un DocField virtuel est pertinent.

Dans l’exemple de la documentation officielle, le DocType Person reçoit trois champs : deux pour le prénom et le nom, stockés en base, et un troisième, « Full Name », qui exploite ces données. Dans ce cas, le champ options reçoit l’expression qui produit la valeur de retour du champ virtuel. La documentation officielle illustre cette configuration par des captures d’écran.

Jusqu’ici, les exemples portent sur des champs qui dépendent d’attributs présents dans le système. Mais le principe s’étend facilement à des données qui ne viennent pas de votre DocType : vous pouvez par exemple récupérer le statut de plusieurs services externes, ou toute autre chose que vous souhaitez afficher à cet endroit.

Comment utiliser un DocField virtuel

La mise en place se fait en deux étapes.

1. Définir le DocField virtuel

La définition est simple : il suffit de cocher la case « Virtual » dans la configuration du DocField. Un DocField virtuel ne crée pas de colonne correspondante dans la table du DocType. Le champ devient donc en lecture seule (Read Only) dans les vues de formulaire.

À retenir

Évitez de rendre virtuels des DocFields existants, sauf si vous savez exactement ce que vous faites.

2. Définir une source pour le champ

La première étape ne crée qu’une sorte d’emplacement réservé pour la valeur. Sans code indiquant ce que le champ doit afficher, il n’y a pas réellement de champ. Deux approches sont possibles.

En étendant le contrôleur du DocType

Il suffit d’ajouter une propriété Python portant le même nom que le champ virtuel. C’est l’approche la plus souple : vous pouvez enchaîner des appels d’API internes, agréger plusieurs sources de données, etc.

python
class Person(Document):
 @property
 def age(self):
 return frappe.utils.now_datetime() - self.creation

En utilisant DocField.options

Cette approche est plus restrictive, car elle permet d’écrire le code directement depuis le bureau (Desk). Les utilitaires autorisés dans les scripts serveur et les attributs du document y sont accessibles. L’équivalent de la propriété ci-dessus s’écrit :

python
frappe.utils.now_datetime() - self.creation

L’exemple Person.full_name évoqué plus haut utilise de la même manière les f-strings de Python.

À retenir

Cette méthode est à privilégier pour des scripts relativement courts. Attention aux erreurs de types incompatibles lors de son utilisation.

Impact sur le fonctionnement interne

Si vous connaissez bien les rouages de Frappe, le comportement de cette fonctionnalité vous paraîtra très prévisible.

API côté serveur

Les méthodes de DatabaseQuery et les API Database ne renvoient pas les valeurs virtuelles, puisqu’elles ne vivent pas dans la base de données du site.

API REST

Les API /api/method/frappe.desk.form.load.getdoc et /api/resource s’appuient sur Document.get_valid_dict, qui calcule aussi les valeurs virtuelles. Ces API servent également au rendu des vues de formulaire du bureau.

Base de données

Les champs virtuels ne laissent aucune trace dans la table du DocType concerné. En revanche, on retrouve des enregistrements correspondants dans les tables Custom Field et DocField, qui stockent les métadonnées (Meta) du DocType.

Tables virtuelles sur des DocTypes non virtuels

Attention

Cette fonctionnalité n’est disponible que dans la version nightly (v16). Elle est considérée comme expérimentale.

Une table enfant virtuelle (virtual child table) est un champ virtuel de type « Table » calculé à l’exécution. Elle se comporte à bien des égards comme une table enfant normale :

  • elle apparaît dans le formulaire (grille) sous le document parent ;
  • ses lignes sont calculées dynamiquement (par exemple via une propriété mise en cache ou un descripteur) ;
  • elle est en lecture seule (impossible d’y écrire avec les méthodes ORM habituelles) ;
  • le DocType parent ne persiste pas ces lignes enfants ; elles n’existent qu’en mémoire ;
  • la méthode descripteur de la table virtuelle peut renvoyer soit des dictionnaires bruts, soit des instances de Document ;
  • lors du chargement depuis la base de données, le descripteur de la table enfant virtuelle est déclenché pour remplir les lignes enfants.

Les tables virtuelles sont utiles pour afficher des données calculées ou agrégées, ou un résumé de données liées stockées ailleurs.

Définir une table enfant virtuelle

Pour définir une table enfant virtuelle, ajoutez une nouvelle entrée de champ dans le DocType parent avec « is virtual » à 1. La logique qui récupère dynamiquement le contenu de la table doit être définie comme propriété mise en cache (cached_property) sur le contrôleur du DocType.

Attention

Vous ne devez PAS utiliser @property pour les tables virtuelles : seuls @cached_property et les autres descripteurs « non-data » équivalents sont pris en charge.

python
class User(…) :
    # This is a cached_property (or a non-data descriptor) returning computed rows
    @cached_property
    def virtual_sessions(self):
        # return a list of dicts or list of Document instances
        sessions = get_session_logs(self.name)
        return sessions

Dans cet exemple, lorsqu’un enregistrement User est chargé, le framework appelle User.virtual_sessions pour récupérer les lignes enfants, puis initialise une table enfant virtuelle virtual_sessions dans le contexte du formulaire.

Pages liées