Contrat d'une ressource admin¶
Une ressource admin décrit comment une entité d'un projet Forge est administrée : quelle entité, sous quel slug d'URL, avec quels libellés, et quels champs sont montrés en liste et éditables en formulaire.
Statut : châssis en cours
Cette page documente le contrat (AdminResource) et le registre (AdminRegistry) livrés par le ticket ADMIN-RESOURCE-CONTRACT-001.
Les vues qui consomment ce contrat viendront par les tickets suivants.
Principe¶
Le contrat est une déclaration Python, pas un nouveau fichier de configuration.
L'entité reste décrite par son contrat JSON ; la ressource admin est une couche de présentation au-dessus, écrite en code explicite et modifiable.
La ressource valide sa propre forme à la construction.
Elle ne lit ni le contrat d'entité ni la base : le rapprochement avec l'entité réelle (existence de l'entité, des champs) relèvera d'une vérification ultérieure (forge admin:doctor).
AdminResource¶
AdminResource est une dataclass immuable (frozen).
| Attribut | Type | Rôle |
|---|---|---|
entity |
str |
nom canonique de l'entité (PascalCase, ex. Article) |
slug |
str |
segment d'URL sous /admin/ (minuscules, tirets, ex. articles) |
label |
str |
libellé singulier |
plural_label |
str |
libellé pluriel |
list_fields |
tuple[str, ...] |
champs affichés en liste (au moins un) |
form_fields |
tuple[str, ...] |
champs éditables en formulaire (au moins un) |
table |
str |
table physique (snake_case) ; non dérivable du nom d'entité |
order_by |
str |
colonne de tri par défaut (vide → premier list_fields) |
pk |
str |
colonne de clé primaire pour la vue détail (défaut id) |
Règles de validation, sinon AdminResourceError :
entityen PascalCase ;slugen minuscules, chiffres et tirets, commençant par une lettre ;labeletplural_labelnon vides ;list_fieldsetform_fieldsnon vides, chaque nom en snake_case, sans doublon ;tableetpken snake_case ;order_by, s'il est fourni, en snake_case.
from forge_mvc_admin import AdminResource
article = AdminResource(
entity="Article",
slug="articles",
label="Article",
plural_label="Articles",
list_fields=("title", "published_at"),
form_fields=("title", "body"),
table="articles",
)
La liste (GET /admin/<slug>) construit un SELECT contraint à partir de ce contrat : seuls la table, les colonnes list_fields et la colonne de tri entrent dans la requête (identifiants validés en liste blanche), la pagination passe par des paramètres.
Aucune introspection, pas d'ORM.
La fiche détail (GET /admin/<slug>/<id>) lit une ligne par sa clé primaire (WHERE <pk> = ?, valeur paramétrée) et affiche pk puis list_fields puis form_fields (colonnes uniques).
La création (GET/POST /admin/<slug>/new) affiche un formulaire des form_fields puis insère une ligne : seules ces colonnes sont écrites (liste blanche), les valeurs sont paramétrées, une valeur vide devient NULL, et le jeton CSRF est vérifié.
En cas de succès, redirection vers la fiche créée.
À ce stade il n'y a pas de validation par champ (pas de contrat au runtime) : les contraintes restent celles de la base.
L'édition (GET/POST /admin/<slug>/<id>/edit) pré-remplit le formulaire depuis la ligne existante puis fait un UPDATE … WHERE <pk> = ? (mêmes garanties que la création : colonnes en liste blanche, valeurs paramétrées, CSRF).
La fiche détail propose un lien « Modifier ».
La suppression est contrôlée : GET /admin/<slug>/<id>/delete affiche une page de confirmation (lecture seule), et seule la soumission POST /admin/<slug>/<id>/delete exécute DELETE … WHERE <pk> = ? LIMIT 1 (jamais en GET, CSRF vérifié), puis redirige vers la liste.
La fiche détail propose un lien « Supprimer »
vers la page de confirmation.
AdminRegistry¶
Le registre rassemble les ressources d'un projet.
Il est explicite : l'application enregistre ses ressources à la main, il n'y a aucune découverte automatique.
Forge Core n'enregistre rien ; le registre par défaut est vide tant que l'application ne l'a pas peuplé.
| Méthode | Rôle |
|---|---|
register(resource) |
enregistre une ressource ; lève si le slug est déjà pris |
get(slug) |
retourne la ressource d'un slug ; lève si inconnue |
all() |
retourne les ressources dans leur ordre d'enregistrement |
slug in registry |
teste la présence d'un slug |
len(registry) |
nombre de ressources |
L'ordre d'enregistrement est préservé : il pilotera l'ordre de la navigation.
from forge_mvc_admin import registry, AdminResource
registry.register(article)
# Ailleurs, l'application peut aussi créer son propre registre :
from forge_mvc_admin import AdminRegistry
mon_registre = AdminRegistry()
Limites assumées¶
- Le contrat ne vérifie pas que l'entité ou les champs existent réellement.
- Aucun rendu, aucune route, aucune action ne sont fournis par ce ticket.
- Les types de champs et les widgets ne sont pas encore pris en compte.