Frappe Framework
Types de DocType
Septième étape du tutoriel : création des DocTypes Library Membership, Library Transaction et Library Settings pour illustrer les champs Link, les documents soumissibles et les DocTypes Single.
Objectif
Découvrons les différents types de DocType du framework en créant de nouveaux DocTypes pour notre app de bibliothèque.
Library Membership
Créons un nouveau DocType : Library Membership (adhésion). Il comporte les champs suivants :
| Libellé du champ | Type | Remarque |
|---|---|---|
| Library Member | Link | Obligatoire (Mandatory) |
| Full Name | Data | Lecture seule (Read Only) |
| From Date | Date | |
| To Date | Date | |
| Paid | Check |
Ce DocType a l’option Is Submittable activée. Son nommage (Naming) est défini sur LMS.##### et il est réservé au rôle Librarian. Enfin, dans la section View Settings, le champ Title Field doit valoir full_name. La documentation officielle illustre cette configuration par une capture d’écran.
Le champ de lien (Link) Library Member s’apparente à une clé étrangère dans d’autres frameworks : il permet de lier la valeur à un enregistrement d’un autre DocType. Ici, il pointe vers un enregistrement du DocType Library Member.
Le champ Full Name est en lecture seule : sa valeur est automatiquement récupérée (fetched from) depuis le champ full_name de l’enregistrement lié dans Library Member.
Ouvrez maintenant la liste Library Membership et créez un nouveau document. Le champ Library Member se présente sous forme de liste déroulante proposant les enregistrements existants. Sélectionnez un membre : le Full Name est récupéré automatiquement.
DocTypes liés
Les DocTypes liés (Linked DocTypes) sont des DocTypes référencés dans d’autres DocTypes par des champs Link. Tous les DocTypes peuvent être liés. On peut classer les DocTypes en deux grandes familles selon le type de données qu’ils stockent : données de référence (Master) et données transactionnelles (Transactional). Article et Library Member sont des données de référence, car ils représentent une entité (physique ou virtuelle). Library Membership stocke des données transactionnelles.
DocTypes soumissibles
Lorsque l’option Is Submittable est activée, le DocType devient soumissible (Submittable). Un document soumissible peut avoir trois états : Draft (brouillon), Submitted (soumis) et Cancelled (annulé).
- À l’état Draft, le document se modifie comme n’importe quel document.
- Une fois Submitted, aucun champ du document ne peut plus être modifié.
- Un document soumis peut être Cancelled, ce qui le rend invalide.
Vous avez peut-être remarqué qu’un champ supplémentaire, Amended From, a été ajouté au DocType Library Membership. Il sert à suivre les amendements des documents : une fois annulé, un document ne peut qu’être amendé, c’est-à-dire dupliqué ; le document annulé est alors lié au nouveau document amendé via le champ Amended From.
Validation dans le contrôleur de Library Membership
Écrivons du code qui garantit, à la création d’une adhésion, que le membre n’a pas déjà une adhésion active.
library_membership.py
import frappe
from frappe.model.document import Document
from frappe.model.docstatus import DocStatus
class LibraryMembership(Document):
# check before submitting this document
def before_submit(self):
exists = frappe.db.exists(
"Library Membership",
{
"library_member": self.library_member,
"docstatus": DocStatus.submitted(),
# check if the membership's end date is later than this membership's start date
"to_date": (">", self.from_date),
},
)
if exists:
frappe.throw("There is an active membership for this member")La logique est placée dans la méthode before_submit, exécutée avant la soumission du document. La méthode frappe.db.exists vérifie s’il existe un enregistrement Library Membership correspondant aux filtres fournis. Si c’est le cas, frappe.throw interrompt l’exécution et affiche un message expliquant la raison à l’utilisateur.
Essayez de créer une adhésion dont la période chevauche une adhésion existante : une erreur s’affiche à la soumission du document.
Library Transaction
Créons un DocType pour enregistrer l’emprunt (Issue) ou le retour (Return) d’un article par un membre disposant d’une adhésion active.
Ce DocType s’appelle Library Transaction et comporte les champs suivants :
| Libellé du champ | Type | Remarque |
|---|---|---|
| Article | Link | Lien vers Article |
| Library Member | Link | Lien vers Library Member |
| Type | Select | Deux options : Issue et Return |
| Date | Date | Date de la transaction |
Ce DocType est lui aussi soumissible.
Validation de la transaction
Lorsqu’un article est emprunté, il faut vérifier que le membre dispose d’une adhésion active et que l’article est disponible. Écrivons ces validations.
library_transaction.py
import frappe
from frappe.model.document import Document
from frappe.model.docstatus import DocStatus
class LibraryTransaction(Document):
def before_submit(self):
if self.type == "Issue":
self.validate_issue()
# set the article status to be Issued
article = frappe.get_doc("Article", self.article)
article.status = "Issued"
article.save()
elif self.type == "Return":
self.validate_return()
# set the article status to be Available
article = frappe.get_doc("Article", self.article)
article.status = "Available"
article.save()
def validate_issue(self):
self.validate_membership()
article = frappe.get_doc("Article", self.article)
# article cannot be issued if it is already issued
if article.status == "Issued":
frappe.throw("Article is already issued by another member")
def validate_return(self):
article = frappe.get_doc("Article", self.article)
# article cannot be returned if it is not issued first
if article.status == "Available":
frappe.throw("Article cannot be returned without being issued first")
def validate_membership(self):
# check if a valid membership exist for this library member
valid_membership = frappe.db.exists(
"Library Membership",
{
"library_member": self.library_member,
"docstatus": DocStatus.submitted(),
"from_date": ("<", self.date),
"to_date": (">", self.date),
},
)
if not valid_membership:
frappe.throw("The member does not have a valid membership")Le code est assez long, mais il se lit facilement ; les commentaires en ligne apportent des explications complémentaires.
Library Settings
Créons le dernier DocType de l’app : Library Settings. Il comporte les champs suivants :
| Libellé du champ | Rôle |
|---|---|
| Loan Period | Définit la durée d’emprunt en nombre de jours |
| Maximum Number of Issued Articles | Limite le nombre maximal d’articles qu’un même membre peut emprunter |
Comme nous n’avons pas besoin de plusieurs enregistrements pour ces paramètres, nous activons Is Single sur ce DocType.
Après la création, cliquez sur Go to Library Settings pour ouvrir le formulaire et renseignez Loan Period et Maximum Number of Issued Articles.
DocTypes Single
Lorsque l’option Is Single est activée, le DocType devient un DocType Single. Il s’apparente aux enregistrements singleton d’autres frameworks : aucune nouvelle table n’est créée. Toutes les valeurs des DocTypes Single sont stockées dans une seule table, tabSingles. Ce type sert généralement à stocker des paramètres globaux.
Utiliser Library Settings dans les validations
Modifions Library Membership pour que To Date soit calculé automatiquement à partir de Loan Period et de From Date.
library_membership.py
from frappe.model.document import Document
from frappe.model.docstatus import DocStatus
import frappe
class LibraryMembership(Document):
# check before submitting this document
def before_submit(self):
exists = frappe.db.exists(
"Library Membership",
{
"library_member": self.library_member,
"docstatus": DocStatus.submitted(),
# check if the membership's end date is later than this membership's start date
"to_date": (">", self.from_date),
},
)
if exists:
frappe.throw("There is an active membership for this member")
# get loan period and compute to_date by adding loan_period to from_date
loan_period = frappe.db.get_single_value("Library Settings", "loan_period")
self.to_date = frappe.utils.add_days(self.from_date, loan_period or 30)La méthode frappe.db.get_single_value récupère la valeur de loan_period dans le DocType Library Settings.
Modifions maintenant Library Transaction pour qu’à l’emprunt d’un article, la limite maximale soit vérifiée.
library_transaction.py
import frappe
from frappe.model.document import Document
from frappe.model.docstatus import DocStatus
class LibraryTransaction(Document):
def before_submit(self):
if self.type == "Issue":
self.validate_issue()
self.validate_maximum_limit()
# set the article status to be Issued
article = frappe.get_doc("Article", self.article)
article.status = "Issued"
article.save()
elif self.type == "Return":
self.validate_return()
# set the article status to be Available
article = frappe.get_doc("Article", self.article)
article.status = "Available"
article.save()
def validate_issue(self):
self.validate_membership()
article = frappe.get_doc("Article", self.article)
# article cannot be issued if it is already issued
if article.status == "Issued":
frappe.throw("Article is already issued by another member")
def validate_return(self):
article = frappe.get_doc("Article", self.article)
# article cannot be returned if it is not issued first
if article.status == "Available":
frappe.throw("Article cannot be returned without being issued first")
def validate_maximum_limit(self):
max_articles = frappe.db.get_single_value("Library Settings", "max_articles")
count = frappe.db.count(
"Library Transaction",
{
"library_member": self.library_member,
"type": "Issue",
"docstatus": DocStatus.submitted(),
},
)
if count >= max_articles:
frappe.throw("Maximum limit reached for issuing articles")
def validate_membership(self):
# check if a valid membership exist for this library member
valid_membership = frappe.db.exists(
"Library Membership",
{
"library_member": self.library_member,
"docstatus": DocStatus.submitted(),
"from_date": ("<", self.date),
"to_date": (">", self.date),
},
)
if not valid_membership:
frappe.throw("The member does not have a valid membership")Nous avons ajouté une méthode validate_maximum_limit qui utilise frappe.db.count pour compter le nombre de transactions effectuées par le membre.
Nous avons ainsi couvert les bases de la création de DocTypes et leurs différents types, et écrit la logique métier de plusieurs DocTypes.
Bravo d’être arrivé jusqu’ici. Continuons !
Étape suivante : Scripts de formulaire.
