2. Les données
This content is not available in your language yet.
L’œuvre
models/title.py déclare ce que la bibliothèque possède intellectuellement.
"""``library_title`` -- a work the library holds, in one or more copies."""
from __future__ import annotations
from typing import TYPE_CHECKING
from kewos.orm.fields import CharField, IntegerField, SelectionFieldfrom kewos.search import SearchableDescriptor, is_searchable, register_searchable
if TYPE_CHECKING: from kewos.orm.registry import Registry
GENRES = [ ("fiction", "Fiction"), ("science", "Science"), ("history", "History"), ("children", "Children"), ("reference", "Reference"),]
def register_title(registry: Registry) -> None: """Declare ``library_title`` and make it searchable and taggable.""" registry.register_model( "library_title", module="library", taggable=True, fields=[ CharField(name="name", nullable=False, max_length=200), CharField(name="author", nullable=False, max_length=120), CharField(name="isbn", nullable=True, max_length=17, unique=True), IntegerField(name="published_year", nullable=True), SelectionField(name="genre", choices=GENRES, default="fiction"), # A long text is a CharField with NO max_length. CharField(name="summary", nullable=True), ], ) if not is_searchable("library_title"): register_searchable( SearchableDescriptor(model="library_title", title="name", subtitle="author") )Trois choses valent d’être remarquées.
unique=True sur l’ISBN pose une contrainte dans le schéma, pas une vérification dans un
formulaire : deux processus qui insèrent le même ISBN en même temps ne peuvent pas passer tous
les deux.
taggable=True branche le modèle sur le système d’étiquettes du noyau. Aucune table à créer,
aucun champ à déclarer.
register_searchable le fait apparaître dans la recherche globale, avec un titre et un
sous-titre. Une recherche métier ne s’écrit jamais à la main dans un module.
Les types de champ
| Type | Pour |
|---|---|
CharField | Un texte. Avec max_length pour une ligne, sans pour un texte long. |
IntegerField, BigIntegerField | Des entiers. |
DecimalField | Tout montant et toute quantité. precision et scale obligatoires. |
BooleanField | Oui ou non. |
DateField, DateTimeField | Une date, un instant. Les instants sont toujours en UTC. |
SelectionField | Une liste fermée de valeurs. |
JsonField, RichTextField | Une structure, un contenu riche. |
FileField, BinaryField | Une pièce jointe, un contenu binaire. |
Many2one, One2many, Many2many | Les relations. |
ComputedField | Une valeur dérivée d’un calcul. |
RelatedField | Un raccourci vers un champ d’une relation. |
AggregateField | Une somme ou un compte sur les enfants d’une relation. |
L’exemplaire, et le champ que tout le monde oublie
def _copy_name(instance: Any) -> str: """``<barcode> - <shelf>``, recomputed on every write.""" shelf = instance.shelf or "?" return f"{instance.barcode} - {shelf}"
def register_copy(registry: Registry) -> None: """Declare ``library_copy``.""" registry.register_model( "library_copy", module="library", fields=[ Many2one(name="title_id", target_model="library_title", nullable=False), CharField(name="barcode", nullable=False, max_length=32, unique=True), CharField(name="shelf", nullable=True, max_length=16), SelectionField(name="condition", choices=CONDITIONS, default="good"), DateField(name="acquired_on", nullable=True), ComputedField( name="name", base_field=CharField(name="name", nullable=True, max_length=64), compute=_copy_name, depends=("barcode", "shelf"), store=True, ), ], )Un ComputedField avec store=True a une vraie colonne : la valeur est recalculée à chaque
écriture par le noyau, et elle est donc triable et cherchable en SQL. N’y affectez jamais
de valeur à la main : le recalcul l’écrase au moment du flush.
Avec store=False, il n’y a pas de colonne : la valeur est recalculée à chaque lecture, et
elle est invisible pour une requête SQL.
La relation
Many2one(name="title_id", target_model="library_title", nullable=False)Rassembler les déclarations
models/__init__.py les appelle dans l’ordre des dépendances : un exemplaire pointe vers
une œuvre, un emprunt vers un exemplaire et un adhérent.
def register_models(registry: Registry) -> None: """Declare every model of the module, dependencies first.""" register_title(registry) register_copy(registry) register_member(registry) register_loan(registry) register_loan_workflow(registry)Des fichiers aux tables
kewos reconcile --confirm --modules-root ./modulesmain [active]: reconciled (0 migration(s), 1 column(s))done: 1 tenant(s), 0 failedLes quatre tables existent :
library_copy library_loan library_member library_titleEt les colonnes sont plus nombreuses que ce que nous avons écrit :
acquired_on barcode condition created_at created_by_id deleted_atdeleted_by_id id kws_x_extra name shelf title_id updated_atupdated_by_id versionCe que le noyau ajoute tout seul
| Champ | Rôle |
|---|---|
id | La clé primaire. |
created_at, created_by_id | Qui a créé, et quand. |
updated_at, updated_by_id | Qui a modifié en dernier, et quand. |
version | Un compteur d’écritures, qui sert à détecter deux modifications concurrentes. |
deleted_at, deleted_by_id | La suppression douce : l’enregistrement disparaît des listes sans quitter la base. |
kws_x_extra | Les champs ajoutés à chaud par le Studio. |
On ne les déclare pas, on ne les redéfinit pas, et on peut compter dessus dans n’importe quel modèle.
L’API, sans l’avoir écrite
curl -X POST http://127.0.0.1:8080/api/v1/library_title \ -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \ -d '{"name":"Dune","author":"Frank Herbert","isbn":"9780441013593", "published_year":1965,"genre":"fiction"}'{"id":1,"name":"Dune","author":"Frank Herbert","isbn":"9780441013593", "published_year":1965,"genre":"fiction","summary":null, "created_at":"2026-09-03T18:24:11.402881+00:00","created_by_id":1, "updated_at":"2026-09-03T18:24:11.402881+00:00","updated_by_id":1, "version":1,"deleted_at":null,"deleted_by_id":null}