Modèle de données¶
Document de référence du schéma de la base SQLite. À tenir à jour avec
le code (src/archives_tool/models/).
Vue d'ensemble¶
Le modèle s'organise autour de trois entités principales — Collection, Item, Fichier — et de tables périphériques pour la configuration, la traçabilité et les intégrations externes.
┌────────────────┐
│ Collection │ une revue, un fonds
└────────┬───────┘
│ 1..n
┌────────▼───────┐ ┌──────────────────┐
│ Item │────▶│ ModificationItem │ journal des édits
└────────┬───────┘ └──────────────────┘
│ 1..n
┌────────▼───────┐ ┌──────────────────┐
│ Fichier │────▶│ OperationFichier │ journal des opérations
└────────────────┘ └──────────────────┘
│
▼
(racine logique + chemin relatif)
┌─────────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ ProfilImport │ │ ChampPersonnalise│ │ ValeurControlee │
└─────────────────┘ └──────────────────┘ └──────────────────┘
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ SourceExterne │───▶│ RessourceExterne │───▶│ LienExterneItem │ (V2+)
└──────────────────┘ └──────────────────┘ └──────────────────┘
Principes structurants¶
1. Hybridation colonnes dédiées / JSON¶
Les champs structurants et récurrents (titre, cote, date, type COAR, état) sont des colonnes dédiées pour permettre indexation, recherche performante, contraintes.
Les champs étendus et variables par collection sont stockés dans un
champ metadonnees de type JSON. Cela accueille la variabilité des
conventions de catalogage sans multiplier les migrations.
Règle : si un champ est interrogé régulièrement ou soumis à contrainte, il devient colonne. Sinon, il reste dans le JSON.
2. Chemins en deux parties¶
Jamais de chemin absolu en base. Toujours (racine_logique, chemin_relatif) :
racine: nom symbolique (scans_revues,miniatures) résolu par la config locale de chaque utilisateur.chemin_relatif: chemin à partir de la racine, en séparateurs POSIX (/) même sous Windows, normalisé en Unicode NFC.
Résolution du chemin absolu au moment de l'accès uniquement.
3. Traçabilité systématique¶
Toutes les entités éditables portent :
- cree_le, cree_par
- modifie_le, modifie_par
- version (entier, incrémenté à chaque modification, utilisé pour
verrou optimiste)
4. États explicites plutôt que booléens¶
Les workflows métier (catalogage, validation, suppression) utilisent des enums d'état et non des booléens. Permet d'affiner sans migration.
5. Suppression logique par défaut¶
Pas de DELETE physique sur les entités métier. Un champ etat permet
la corbeille logique. Purge explicite séparée, journalisée.
Enums¶
Définis côté Python avec enum.StrEnum, stockés en TEXT en base.
EtatCatalogage¶
Progression du catalogage d'un item.
| Valeur | Description |
|---|---|
brouillon |
Saisie incomplète, travail en cours. |
a_verifier |
Saisie terminée, en attente de contrôle. |
verifie |
Contrôlé, prêt pour validation finale. |
valide |
Notice définitive. |
a_corriger |
Anomalie détectée, retour au catalogueur. |
EtatFichier¶
| Valeur | Description |
|---|---|
actif |
Fichier courant, utilisé. |
remplace |
Remplacé par une version plus récente, conservé pour historique. |
corbeille |
Supprimé logiquement, restaurable. |
PhaseChantier¶
Phase courante d'un chantier de catalogage. Pilote l'affichage et permettra des filtres « collections en cours » plus tard.
| Valeur | Description |
|---|---|
numerisation |
Scans en cours de production. |
catalogage |
Saisie initiale des notices. |
revision |
Relecture, vérifications croisées. |
finalisation |
Validation finale, préparation export. |
archivee |
Chantier clos, plus de modifications attendues. |
en_pause |
Travail suspendu, à reprendre plus tard. |
TypePage¶
Typologie métier des scans.
| Valeur | Description |
|---|---|
couverture |
Première de couverture. |
dos_couverture |
Verso de couverture. |
page_titre |
Page de titre. |
page |
Page courante (cas général). |
planche |
Planche hors-texte, illustration. |
supplement |
Encart, supplément. |
quatrieme |
Quatrième de couverture. |
autre |
Hors nomenclature (rare, préciser en notes). |
TypeOperationFichier¶
| Valeur |
|---|
rename |
move |
delete |
restore |
replace |
StatutOperation¶
| Valeur |
|---|
simulee |
reussie |
echouee |
annulee |
RoleCollaborateur¶
Vocabulaire fermé des rôles techniques d'un collaborateur de collection. Toute extension demande une migration (l'enum est validée applicativement contre cette liste).
| Valeur | Libellé |
|---|---|
numerisation |
Numérisation |
transcription |
Transcription |
indexation |
Indexation |
catalogage |
Catalogage |
TypeCollection¶
Distingue les deux espèces de collections (V0.9.0-alpha).
| Valeur | Description |
|---|---|
miroir |
Collection créée automatiquement avec un fonds, regroupe par défaut tous ses items. Toujours rattachée à un fonds (CHECK constraint). |
libre |
Collection créée manuellement. Rattachée à un fonds (fonds_id non NULL) ou transversale (fonds_id IS NULL). |
Tables¶
Identité¶
Il n'y a pas de table utilisateur. Chaque poste est configuré avec
un nom libre dans la config locale (utilisateur: "Marie"). Ce nom
est copié tel quel dans les champs d'audit des tables (cree_par,
modifie_par, ajoute_par, execute_par). Aucune FK, aucune
contrainte d'unicité : l'information est uniquement informative.
fonds (V0.9.0-alpha)¶
Le corpus brut : matériel issu d'une source identifiée (un don, un fonds éditorial, une numérisation), interne à l'outil. Nakala ne connaît pas cette notion. Chaque fonds porte exactement une collection miroir créée automatiquement à sa création.
| Colonne | Type | Contraintes | Notes |
|---|---|---|---|
id |
INTEGER | PK | |
cote |
VARCHAR(64) | UNIQUE, NOT NULL | Ex. HK, FA, CONC-1789. |
titre |
VARCHAR(500) | NOT NULL | |
description |
TEXT | Description courte (interne ou publique selon usage). | |
description_publique |
TEXT | Réservée à l'export Nakala. | |
description_interne |
TEXT | Notes équipe, conventions de chantier. | |
personnalite_associee |
VARCHAR(255) | Personne/mouvement/institution autour de qui s'organise le fonds. | |
responsable_archives |
VARCHAR(255) | Personne ou institution responsable de la constitution. | |
editeur |
VARCHAR(255) | Champs périodique : présents si le fonds ressemble à une revue. | |
lieu_edition |
VARCHAR(255) | ||
periodicite |
VARCHAR(64) | ||
issn |
VARCHAR(32) | ||
date_debut |
VARCHAR(64) | EDTF tolérant. | |
date_fin |
VARCHAR(64) | ||
cree_le / cree_par / modifie_le / modifie_par / version |
TracabiliteMixin |
Index : cote, titre.
Cascade : supprimer un fonds supprime ses items et sa collection
miroir ; les collections libres rattachées passent à transversales
(fonds_id = NULL via FK ON DELETE SET NULL).
collection (refondue V0.9.0-alpha)¶
Un classement publiable : sélection d'items pour une présentation,
un thème, un export Nakala. Distingué du fonds par
type_collection.
| Colonne | Type | Contraintes | Notes |
|---|---|---|---|
id |
INTEGER | PK | |
cote |
VARCHAR(64) | NOT NULL | Plus globalement unique ; unique par fonds via (fonds_id, cote). |
titre |
VARCHAR(500) | NOT NULL | |
type_collection |
VARCHAR(20) | NOT NULL, DEFAULT libre |
miroir ou libre (cf. TypeCollection). |
fonds_id |
INTEGER | FK → fonds.id ON DELETE SET NULL |
NULL pour une collection libre transversale. |
phase |
VARCHAR(20) | NOT NULL, DEFAULT catalogage |
|
description / description_publique / description_interne |
TEXT | ||
personnalite_associee / responsable_archives |
VARCHAR(255) | ||
editeur / lieu_edition / periodicite / issn |
varchars | Champs périodique conservés (si la collection ressemble à une revue, par exemple une miroir d'un fonds-revue). | |
date_debut / date_fin |
VARCHAR(50) | ||
doi_nakala |
TEXT | UNIQUE | DOI de la collection sur Nakala. |
doi_collection_nakala_parent |
VARCHAR(128) | Rattachement à une collection Nakala parente (sans contrainte d'unicité). | |
metadonnees / notes_internes |
JSON / TEXT | ||
profil_import_id |
INTEGER | FK → profil_import.id |
|
cree_le / cree_par / modifie_le / modifie_par / version |
TracabiliteMixin |
Index :
- (fonds_id, cote) UNIQUE : cote unique par fonds.
- cote, titre, fonds_id, doi_nakala.
CHECK constraint : (type_collection = 'libre') OR (fonds_id IS NOT NULL) —
une miroir doit toujours pointer vers son fonds.
Invariants¶
- Tout fonds a exactement une collection MIROIR (création au service
fonds). - Une collection MIROIR a toujours
fonds_idnon NULL (CHECK). - Une collection LIBRE peut être rattachée (
fonds_idnon NULL) ou transversale (fonds_id IS NULL). - Tout item a
fonds_idnon NULL. - À la création d'un Fonds : la miroir est créée avec la même cote et le même titre.
- À l'ajout d'un Item dans un fonds : il est ajouté à la miroir (à charger côté service
items— V0.9.0-alpha.1). - Un item peut être retiré manuellement de sa miroir sans être supprimé du fonds.
- Suppression d'un Fonds : items + miroir supprimés, libres rattachées passent transversales.
- Une cote de fonds peut coïncider avec la cote d'une collection libre (cas de la miroir).
item (refondu V0.9.0-alpha)¶
L'unité principale de catalogage : un numéro, un volume, un document.
Appartient à exactement un fonds (FK obligatoire) et peut figurer
dans 0..N collections via la junction item_collection.
| Colonne | Type | Contraintes | Notes |
|---|---|---|---|
id |
INTEGER | PK | |
fonds_id |
INTEGER | FK → fonds.id ON DELETE CASCADE, NOT NULL |
Le matériel d'origine. |
cote |
VARCHAR(128) | NOT NULL | Unique par fonds via (fonds_id, cote). Plus globalement unique. |
numero |
TEXT | Peut être 47, 47-48, iv, etc. |
|
numero_tri |
INTEGER | Pour tri numérique fiable | |
titre |
TEXT | Titre propre du numéro si pertinent | |
date |
TEXT | Format EDTF | |
annee |
INTEGER | Pour filtre/tri rapide | |
type_coar |
TEXT | URI COAR, ex. http://purl.org/coar/resource_type/c_2fe3 |
|
langue |
TEXT | ISO 639-3 | |
doi_nakala |
TEXT | UNIQUE | DOI Nakala de l'item. Unique : un DOI ne référence qu'un seul item local. |
doi_collection_nakala |
TEXT | DOI de la collection Nakala de rattachement. Non-unique : plusieurs items partagent la même collection Nakala. | |
description |
TEXT | Résumé, sommaire | |
metadonnees |
JSON | Champs étendus (auteurs multiples, sujets, relations...) | |
etat_catalogage |
TEXT | NOT NULL, DEFAULT brouillon |
Enum |
notes_internes |
TEXT | ||
cree_le |
DATETIME | NOT NULL | |
cree_par |
TEXT | Nom libre copié de la config locale. | |
modifie_le |
DATETIME | ||
modifie_par |
TEXT | Idem. | |
version |
INTEGER | NOT NULL, DEFAULT 1 |
Contraintes :
- UNIQUE (fonds_id, cote)
- UNIQUE (doi_nakala)
- CHECK sur etat_catalogage (valeurs enum)
Index : fonds_id, annee, etat_catalogage, doi_nakala,
doi_collection_nakala. (FTS5 prévu mais à reconstruire en
V0.9.0-gamma sur la nouvelle forme.)
item_collection (V0.9.0-alpha)¶
Liaison N-N entre item et collection. Un item est typiquement
dans la miroir de son fonds et, optionnellement, dans des collections
libres (rattachées au même fonds ou transversales).
| Colonne | Type | Contraintes |
|---|---|---|
item_id |
INTEGER | PK, FK → item.id ON DELETE CASCADE |
collection_id |
INTEGER | PK, FK → collection.id ON DELETE CASCADE |
ajoute_le |
DATETIME | NOT NULL, server_default=now() |
ajoute_par |
VARCHAR(255) |
Note sur metadonnees JSON : structure recommandée :
{
"auteurs": [
{"nom": "Dupont", "prenom": "Jean", "orcid": "0000-..."}
],
"sujets": ["Histoire", "XIXe siècle"],
"relations": [
{"type": "partie_de", "ref": "item:42"},
{"type": "supplement_de", "ref": "nakala:10.34847/nkl.xxx"}
],
"champs_collection": {
"rubrique": "Littérature",
"illustrateurs": ["..."]
}
}
fichier¶
Un scan ou document rattaché à un item.
| Colonne | Type | Contraintes | Notes |
|---|---|---|---|
id |
INTEGER | PK | |
item_id |
INTEGER | FK → item.id, NOT NULL |
|
racine |
TEXT | Nom logique de la racine. NULL si fichier exclusivement référencé via iiif_url_nakala. |
|
chemin_relatif |
TEXT | POSIX, NFC. NULL si fichier exclusivement Nakala. | |
nom_fichier |
TEXT | NOT NULL | Pour recherche rapide |
apercu_chemin |
TEXT | Chemin relatif sous la racine miniatures du JPEG aperçu (1200 px). Rempli par derivatives. |
|
vignette_chemin |
TEXT | Chemin relatif sous la racine miniatures du JPEG vignette (300 px). Rempli par derivatives. |
|
dzi_chemin |
TEXT | Réservé V2+ : chemin du DZI local (tuiles). Jamais rempli en V0.6. | |
iiif_url_nakala |
TEXT | URL info.json IIIF du fichier déposé sur Nakala. Source primaire pour la visionneuse quand renseigné. | |
hash_sha256 |
TEXT | Calculé à l'import, vérifié périodiquement | |
taille_octets |
INTEGER | ||
format |
TEXT | tiff, jpeg, pdf... |
|
largeur_px |
INTEGER | Pour images | |
hauteur_px |
INTEGER | Pour images | |
ordre |
INTEGER | NOT NULL | Position dans l'item |
type_page |
TEXT | NOT NULL, DEFAULT page |
Enum |
folio |
TEXT | Numérotation logique (« iv », « 12bis ») | |
etat |
TEXT | NOT NULL, DEFAULT actif |
Enum |
derive_genere |
BOOLEAN | NOT NULL, DEFAULT 0 | Vignette/aperçu générés ? |
notes_techniques |
TEXT | ||
ajoute_le |
DATETIME | NOT NULL | |
ajoute_par |
TEXT | Nom libre copié de la config locale. | |
modifie_le |
DATETIME | ||
version |
INTEGER | NOT NULL, DEFAULT 1 |
Contraintes :
- UNIQUE (racine, chemin_relatif) — un fichier n'existe qu'une fois.
- UNIQUE (item_id, ordre) — pas de collision d'ordre dans un item.
- CHECK chemin_relatif IS NOT NULL OR iiif_url_nakala IS NOT NULL —
un fichier doit avoir au moins une source (locale ou IIIF).
Index : item_id, hash_sha256, nom_fichier, etat.
profil_import¶
Décrit comment importer les métadonnées et fichiers d'une collection.
| Colonne | Type | Contraintes |
|---|---|---|
id |
INTEGER | PK |
nom |
TEXT | UNIQUE, NOT NULL |
description |
TEXT | |
chemin_yaml |
TEXT | NOT NULL — chemin relatif vers profiles/xxx.yaml |
contenu |
JSON | Copie du YAML parsé, snapshot |
cree_le |
DATETIME | NOT NULL |
modifie_le |
DATETIME |
Le YAML source reste dans profiles/ (versionné Git). La base stocke
un snapshot pour retracer ce qui a été appliqué lors d'un import.
champ_personnalise¶
Définit les champs étendus utilisables par une collection.
| Colonne | Type | Contraintes | Notes |
|---|---|---|---|
id |
INTEGER | PK | |
collection_id |
INTEGER | FK → collection.id |
NULL = champ global |
cle |
TEXT | NOT NULL | Clé JSON, ex. illustrateur |
libelle |
TEXT | NOT NULL | Affichage UI |
type |
TEXT | NOT NULL | texte, texte_long, date_edtf, liste, liste_multiple, reference |
obligatoire |
BOOLEAN | NOT NULL, DEFAULT 0 | |
valeurs_controlees_id |
INTEGER | FK → vocabulaire.id |
Pour listes |
ordre |
INTEGER | NOT NULL | Ordre d'affichage |
aide |
TEXT | Infobulle | |
description_interne |
TEXT | Documentation longue pour l'équipe : pourquoi ce champ, comment le remplir. |
Contraintes : UNIQUE (collection_id, cle).
vocabulaire et valeur_controlee¶
Pour les listes contrôlées (types COAR, langues, vocabulaires métier).
vocabulaire :
| Colonne | Type | Contraintes |
|---|---|---|
id |
INTEGER | PK |
code |
TEXT | UNIQUE, NOT NULL — ex. coar_resource_types |
libelle |
TEXT | NOT NULL |
description |
TEXT | Description publique du vocabulaire. |
description_interne |
TEXT | Documentation équipe (conventions, périmètre). |
uri_base |
TEXT |
valeur_controlee :
| Colonne | Type | Contraintes |
|---|---|---|
id |
INTEGER | PK |
vocabulaire_id |
INTEGER | FK, NOT NULL |
code |
TEXT | NOT NULL |
libelle |
TEXT | NOT NULL |
uri |
TEXT | |
description_interne |
TEXT | Documentation équipe sur la valeur. |
parent_id |
INTEGER | FK → valeur_controlee.id — pour hiérarchies |
ordre |
INTEGER | |
actif |
BOOLEAN | NOT NULL, DEFAULT 1 |
Contrainte : UNIQUE (vocabulaire_id, code).
operation_fichier¶
Journal des opérations sur fichiers (renommage, déplacement, suppression).
| Colonne | Type | Contraintes | Notes |
|---|---|---|---|
id |
INTEGER | PK | |
batch_id |
TEXT | NOT NULL | UUID regroupant un lot |
fichier_id |
INTEGER | FK → fichier.id |
NULL si fichier détruit |
type_operation |
TEXT | NOT NULL | Enum |
racine_avant |
TEXT | ||
chemin_avant |
TEXT | ||
racine_apres |
TEXT | ||
chemin_apres |
TEXT | ||
hash_avant |
TEXT | Vérification intégrité | |
hash_apres |
TEXT | ||
statut |
TEXT | NOT NULL | Enum |
message |
TEXT | Erreur ou info | |
execute_le |
DATETIME | NOT NULL | |
execute_par |
TEXT | Nom libre copié de la config locale. | |
annule_par_batch_id |
TEXT | Batch qui a annulé |
Index : batch_id, fichier_id, execute_le.
modification_item¶
Journal des modifications de métadonnées sur les items.
| Colonne | Type | Contraintes |
|---|---|---|
id |
INTEGER | PK |
item_id |
INTEGER | FK → item.id, NOT NULL |
champ |
TEXT | NOT NULL — nom de colonne ou clé JSON |
valeur_avant |
TEXT | JSON sérialisé si complexe |
valeur_apres |
TEXT | |
modifie_le |
DATETIME | NOT NULL |
modifie_par |
TEXT |
Index : item_id, modifie_le.
operation_import¶
Journal des imports depuis un profil YAML. Une entrée par exécution
réelle (pas en dry-run). Le rapport_json contient la sérialisation
complète du RapportImport pour inspection future.
| Colonne | Type | Contraintes | Notes |
|---|---|---|---|
id |
INTEGER | PK | |
batch_id |
TEXT | NOT NULL, UNIQUE | UUID du lot. Lié aux operation_fichier éventuellement produites pendant l'import. |
profil_chemin |
TEXT | NOT NULL | Chemin du profil YAML utilisé. |
collection_id |
INTEGER | FK → collection.id |
|
items_crees |
INTEGER | ||
items_mis_a_jour |
INTEGER | ||
items_inchanges |
INTEGER | ||
fichiers_ajoutes |
INTEGER | ||
execute_le |
DATETIME | NOT NULL | |
execute_par |
TEXT | Nom libre copié de la config locale. | |
rapport_json |
TEXT | Sérialisation JSON du RapportImport. |
Contrainte : UNIQUE (batch_id).
Index : batch_id.
preferences_affichage¶
Persiste l'ordre des colonnes choisi par un utilisateur dans une vue tabulaire (items, fichiers, sous-collections). Une entrée par combinaison (utilisateur, collection, vue). Pas d'utilisation effective avant V0.6 ; structure créée pour ne pas avoir à reprendre la migration plus tard.
| Colonne | Type | Contraintes | Notes |
|---|---|---|---|
id |
INTEGER | PK | |
utilisateur |
TEXT | NOT NULL | Nom libre copié de la config locale. |
collection_id |
INTEGER | FK → collection.id (CASCADE) |
NULL = préférences globales (vue dashboard, toutes collections). |
vue |
TEXT | NOT NULL | items, fichiers, sous_collections, etc. |
colonnes_ordonnees |
JSON | NOT NULL | Liste de noms de colonnes dans l'ordre voulu. |
cree_le |
DATETIME | NOT NULL | |
modifie_le |
DATETIME |
Contrainte : UNIQUE (utilisateur, collection_id, vue).
Index : utilisateur, collection_id.
collaborateur_collection¶
Personnes ayant contribué techniquement à la constitution d'une
collection (numérisation, transcription, indexation, catalogage).
Pas de FK utilisateur — le nom est texte libre, identique au modèle
d'audit cree_par / modifie_par. Une personne peut porter
plusieurs rôles ; le stockage se fait en JSON.
| Colonne | Type | Contraintes | Notes |
|---|---|---|---|
id |
INTEGER | PK | |
collection_id |
INTEGER | FK → collection.id (CASCADE) |
Indexé. |
nom |
VARCHAR(255) | NOT NULL | Texte libre. |
roles |
JSON | NOT NULL | Liste de valeurs RoleCollaborateur (chaînes). Au moins un rôle, validation applicative. |
periode |
VARCHAR(64) | NULL | EDTF tolérant (« 2022 », « 2022-2023 »). |
notes |
TEXT | NULL | Texte libre. |
cree_le |
DATETIME | NOT NULL | |
modifie_le |
DATETIME | NOT NULL | Mise à jour automatique. |
Index : collection_id.
Les filtres SQL natifs sur les rôles ne sont pas possibles avec le stockage JSON ; c'est accepté pour V0.8.0 — pas de besoin de recherche transverse pour l'instant.
collaborateur_fonds (V0.9.0-alpha)¶
Analogue de collaborateur_collection mais rattaché au fonds. C'est
l'usage par défaut pour les contributeurs d'un corpus ; les
collaborateurs propres à une collection particulière restent dans
collaborateur_collection.
Mêmes colonnes que collaborateur_collection (nom, roles JSON,
periode, notes, cree_le, modifie_le) avec FK fonds_id → fonds.id
ON DELETE CASCADE. Index sur fonds_id.
Services CRUD (V0.9.0-alpha.1)¶
Les services Python applicatifs garantissent les invariants au-dessus du schéma SQL. Trois services principaux :
services/fonds.py¶
creer_fonds(formulaire)→ crée le fonds + sa miroir dans la même transaction (invariants 1, 5).lire_fonds/lire_fonds_par_cote/lister_fonds(compteurs agrégés en 3 queries, pas de N+1).modifier_fonds,supprimer_fonds(cascade items + miroir, libres rattachées passent transversales viaON DELETE SET NULL).
services/collections.py¶
Gère uniquement les collections libres. Les miroirs sont créées
et supprimées par services/fonds.py.
creer_collection_libre(formulaire)→ vérifie le fonds_id si fourni, IntegrityError sur conflit(fonds_id, cote).lire_collection,lire_collection_par_cote(cote, fonds_id=None)(lèveOperationCollectionInterditesi plusieurs matches sans fonds).lister_collections(fonds_id=None, type_collection=None).modifier_collection: refuse de changerfonds_idd'une miroir.supprimer_collection_libre: refuse les miroirs.ajouter_item_a_collection,retirer_item_de_collection: idempotents, valident l'existence des entités.
services/items.py¶
creer_item(formulaire)→ ajoute automatiquement à la miroir du fonds (invariant 6) ; lève si miroir absente (anomalie).lire_item,lire_item_par_cote(cote, fonds_id)(fonds_id obligatoire, cote n'étant pas globalement unique).collections_de_item: requête SQL fraîche via la junction (la relation ORMItem.collectionspeut être obsolète après écritures directes).lister_items_fonds,lister_items_collection: pagination + tri whitelisté + filtre etat_catalogage, retournentListage[ItemResume](réutiliseservices/tri.py).modifier_item:fonds_idest immuable (OperationItemInterditesi changement).supprimer_item: cascade fichiers + liaisons N-N.
Erreurs partagées (services/_erreurs.py)¶
EntiteIntrouvable(LookupError)→ 404.FormulaireInvalide(ValueError)avec dicterreurschamp→message → 400.OperationInterdite(Exception)→ 409.
Chaque service spécialise (FondsIntrouvable, CollectionInvalide, etc.)
pour les try/except typés et des messages contextualisés.
Base de démonstration (V0.9.0-alpha.2)¶
archives-tool demo init produit data/demo.db via le seeder
archives_tool.demo.peupler_base. Composition reproductible (RNG
seedé à 42 par défaut) :
| Fonds | Cote | Items | Caractéristique |
|---|---|---|---|
| Hara-Kiri | HK |
40 | Revue satirique, Cavanna |
| Fonds Aínsa | FA |
167 | Fonds personnel + 4 libres rattachées |
| Revue des Deux Mondes | RDM |
36 | Bimensuel, ISSN 0035-1962 |
| Marges | MAR |
40 | Zine personnel |
| Concorde 1789 | CONC-1789 |
50 | Fonds historique révolutionnaire |
Collections libres rattachées au fonds Aínsa :
FA-OEUVRES (39), FA-CORRESP (32), FA-DOCU (47),
FA-PHOTOS (49). Chaque item est dans la miroir et dans sa
libre (multi-appartenance, invariant 6).
Collection transversale : TEMOIG (« Témoignages d'exil »),
sans fonds_id. Pioche 12 items dans Aínsa (œuvres + correspondance)
et 6 dans Concorde — démontre la transversalité possible des
collections libres.
Collaborateurs seedés sur HK, FA, RDM via CollaborateurFonds
(insertion directe — pas de service CRUD dédié pour l'instant ;
suivra avec les routes web V0.9.0-beta).
Total : 5 fonds, 10 collections, 333 items, ~1300 fichiers. Les fichiers sont des entrées DB seulement (pas de scan physique sur disque) ; l'UI V0.9.0-beta gérera le cas « fichier référencé absent ».
Tests d'intégrité (composition + invariants 1, 4, 6 +
reproductibilité) dans tests/test_demo_seeder.py.
Routes web¶
| Route | Méthode | Notes |
|---|---|---|
/ |
GET | Dashboard : arborescence dépliable fonds → collections + transversales. |
/fonds |
GET | Table simple des fonds (alternative au dashboard). |
/fonds/{cote} |
GET | Page fonds : bandeau métadonnées, collections, collaborateurs (par rôle), items récents. |
/fonds/{cote}/modifier |
GET / POST | Édition complète du fonds, cote verrouillée. PRG : 303 vers /fonds/{cote} au succès, 400 + re-render au refus. |
/fonds/{cote}/collaborateurs |
POST | Ajout d'un CollaborateurFonds. |
/fonds/{cote}/collaborateurs/{id} |
POST | Modification. |
/fonds/{cote}/collaborateurs/{id}/supprimer |
POST | Suppression dure. |
/collection/{cote}?fonds=COTE |
GET | Page collection (3 variantes : miroir, libre rattachée, transversale). Tableau d'items paginé (page, par_page, tri, ordre, etat). Précédence : si la cote matche un fonds et qu'aucun ?fonds= n'est passé, redirige 303 vers /fonds/{cote}. |
/collection/{cote}/modifier |
GET / POST | Édition (libres uniquement). 403 sur miroir. PRG : 303 au succès, 400 + re-render au refus. |
/collection/{cote}/items/picker |
GET | Picker pour ajouter des items (filtre fonds, recherche, pagination). 403 sur miroir. |
/collection/{cote}/items |
POST | Ajout multi-id idempotent depuis le picker. 403 sur miroir. |
/collection/{cote}/items/{id}/retirer |
POST | Retrait d'un item, idempotent. Permis sur miroir (l'item reste dans le fonds, invariant 7). |
/item/{cote}?fonds=COTE |
GET | Placeholder ; page complète en V0.9.0-beta.3. ?fonds= obligatoire. |
Convention ?fonds= en query string : les cotes d'items et de
collections (libres) ne sont uniques que par fonds. La query string
désambiguïse. Pour les collections, la précédence par défaut envoie
sur le fonds homonyme s'il existe.
Anti-confused-deputy : les routes mutantes
(/fonds/{cote}/collaborateurs/{id}/...) vérifient que le
collaborateur appartient bien au fonds passé dans le chemin (404
sinon).
Services dashboard (services/dashboard.py) :
- composer_dashboard(db) : tous les fonds + transversales pour /.
- composer_page_fonds(db, cote) : FondsDetail (fonds + collections
+ items récents + collaborateurs groupés par rôle).
- composer_page_collection(db, collection) : CollectionDetail
avec fonds parent ou fonds représentés selon le type.
Tous en agrégats SQL — pas de N+1.
Pages Fonds et Collection — variantes d'édition¶
Fonds (édition) — formulaire complet ; cote verrouillée
(disabled côté HTML, ignorée côté serveur — la valeur du chemin est
imposée). Toute autre modification est libre. PRG : redirection au
succès, re-render avec dict erreurs au refus.
Collection (édition) — V0.9.0-beta.2.1 :
- MIROIR : 403 sur la route d'édition (pas de bouton « Modifier »
dans la lecture). Pas d'API publique pour muter le titre / la
cote / le fonds_id.
- LIBRE rattachée : édition complète sauf fonds_id (verrouillé
côté serveur — le formulaire n'expose pas le champ ; pour changer
de nature on supprime / recrée).
- LIBRE transversale : idem rattachée, sans fonds_id.
Items dans une collection (V0.9.0-beta.2.1) :
- Tableau paginé directement sur GET /collection/{cote} (pas de
page séparée). Tri whitelist (cote, titre, date, annee,
etat, modifie), filtre etat, pagination par_page 10–200.
- Ajout via picker (/items/picker GET → soumission POST /items)
multi-id idempotent. Filtre fonds par défaut = fonds parent pour
une libre rattachée, tous fonds pour une transversale.
- Retrait via bouton par ligne, POST /items/{id}/retirer.
Idempotent. Permis sur miroir aussi (invariant 7 : l'item reste
dans le fonds).
Sources externes (V2+)¶
source_externe¶
| Colonne | Type | Contraintes |
|---|---|---|
id |
INTEGER | PK |
code |
TEXT | UNIQUE, NOT NULL — nakala, hal, gallica |
libelle |
TEXT | NOT NULL |
type_api |
TEXT | NOT NULL — rest, oai-pmh, iiif |
url_base |
TEXT | NOT NULL |
ttl_cache_heures |
INTEGER | NOT NULL, DEFAULT 24 |
actif |
BOOLEAN | NOT NULL, DEFAULT 1 |
Notes : la clé API éventuelle n'est pas stockée en base. Elle reste dans la config locale de chaque utilisateur (chiffrée si sensible).
ressource_externe¶
Cache local des ressources consultées.
| Colonne | Type | Contraintes |
|---|---|---|
id |
INTEGER | PK |
source_id |
INTEGER | FK → source_externe.id, NOT NULL |
identifiant_externe |
TEXT | NOT NULL — DOI, handle, URI |
type |
TEXT | data, collection |
titre |
TEXT | |
auteurs |
JSON | |
date |
TEXT | EDTF |
metadonnees_brutes |
JSON | Réponse API complète |
manifeste_iiif |
TEXT | URL du manifeste IIIF si disponible |
recupere_le |
DATETIME | NOT NULL |
statut |
TEXT | NOT NULL — actif, introuvable, erreur |
Contrainte : UNIQUE (source_id, identifiant_externe).
lien_externe_item¶
Rattachement optionnel entre un item local et une ressource externe.
| Colonne | Type | Contraintes |
|---|---|---|
id |
INTEGER | PK |
item_id |
INTEGER | FK → item.id, NOT NULL |
ressource_externe_id |
INTEGER | FK, NOT NULL |
type_relation |
TEXT | NOT NULL — meme_ressource, partie_de, supplement_de, evoque |
notes |
TEXT | |
cree_le |
DATETIME | NOT NULL |
cree_par |
TEXT |
Contrainte : UNIQUE (item_id, ressource_externe_id, type_relation).
Recherche plein texte¶
Utiliser SQLite FTS5, table virtuelle item_fts synchronisée via
triggers :
CREATE VIRTUAL TABLE item_fts USING fts5(
titre, description, metadonnees_texte,
content='item', content_rowid='id',
tokenize='unicode61 remove_diacritics 2'
);
Le tokenizer unicode61 remove_diacritics 2 gère les accents français
correctement. Triggers AFTER INSERT / UPDATE / DELETE sur item pour
maintenir l'index.
Pragmas SQLite recommandés¶
À appliquer à l'ouverture de chaque connexion :
PRAGMA journal_mode=WAL;
PRAGMA synchronous=NORMAL;
PRAGMA foreign_keys=ON;
PRAGMA temp_store=MEMORY;
PRAGMA mmap_size=268435456; -- 256 MB
Note : si passage sur partage réseau, repasser en journal_mode=DELETE.
Questions ouvertes sur le modèle¶
- [ ] Gestion des collections imbriquées (un fonds contenant plusieurs
revues) : ajouter
collection.parent_idou garder plat ? - [ ] Droits par collection : table
droit_collectionou rester ouvert à tous ? - [ ] Champs d'item multivalués natifs (auteurs, sujets) : table
dédiée
item_auteur/item_sujetou JSON ? Recommandation actuelle : JSON pour la souplesse, table dédiée si on a besoin d'interroger/dédoublonner. - [ ] Versioning des fichiers (historique des remplacements) : table
fichier_versionou étatremplacesuffit ? Recommandation actuelle : V3, étatremplacesuffit pour la V1. - [ ] Représentation précise des dates EDTF : stockage brut + parsing
applicatif, ou colonnes calculées
date_min/date_maxpour filtrage ?
Évolutivité¶
Le modèle est pensé pour évoluer sans migration lourde sur les cas fréquents :
- Nouveau champ métier spécifique : ajouter dans
metadonneesJSON. - Nouvelle collection avec champs propres : déclarer dans
champ_personnalise. - Nouveau vocabulaire contrôlé :
vocabulaire+valeur_controlee. - Nouveau connecteur externe : ajouter une
source_externe.
Les migrations Alembic restent nécessaires pour les colonnes dédiées et les contraintes structurantes.