Aller au contenu

Modifier un enregistrement

Objectif : éditer une note existante via un formulaire pré-rempli.

Ce que vous allez apprendre : la première écriture du niveau. Le formulaire est chargé avec la valeur courante (fetch_one), puis l'enregistrement passe par core.database.db.execute("UPDATE … WHERE id = ?"), en POST protégé par CSRF.

Jusqu'ici, le Carnet de notes est en lecture seule : lister, rechercher, paginer.

Nous ajoutons la première écriture.
C'est un nouveau besoin (un formulaire, une route POST, du CSRF), donc deux nouvelles méthodes sur NoteController et deux nouvelles routes.

Documentations

Pour bien comprendre ce palier :

Document Ce qu'il apporte
La protection CSRF pourquoi le POST porte un jeton, et le 403 sinon
La session HTTP où vit le jeton garanti par _start_session
L'objet Request route(...) pour l'id, form(...) pour la saisie
Contrôleurs

Complétez les imports et ajoutez le helper de session, les deux requêtes et les deux méthodes dans mvc/controllers/note_controller.py.
Le jeton CSRF vit dans la session : sans session active il serait vide et le POST serait refusé.
Le helper _start_session garantit une session (comme au palier CSRF du niveau débutant) :

from core.database.db import execute, fetch_all, fetch_one
from core.security.cookies import set_session_cookie
from core.security.session import get_session, get_session_id
from core.sessions.manager import get_session_store

SELECT_ONE = "SELECT id, content FROM notes WHERE id = ?"
UPDATE_ONE = "UPDATE notes SET content = ? WHERE id = ?"


class NoteController(BaseController):

    # … index(...) inchangé …

    @staticmethod
    def _start_session(request: Request):
        """Garantit une session active et renvoie (session_id, csrf_token)."""
        session_id = get_session_id(request)
        if session_id is None or get_session(session_id) is None:
            session_id = get_session_store().create()
        session = get_session(session_id) or {}
        return session_id, session.get("csrf_token", "")

    @staticmethod
    def edit(request: Request) -> Response:
        record_id = int(request.route("id", default="0"))
        note = fetch_one(SELECT_ONE, (record_id,))
        if note is None:
            return Response.text("Note introuvable.", status=404)
        session_id, csrf_token = NoteController._start_session(request)
        response = BaseController.render(
            "note/edit.html",
            request=request,
            context={"note": note, "csrf_token": csrf_token},
        )
        set_session_cookie(response, session_id)
        return response

    @staticmethod
    def update(request: Request) -> Response:
        record_id = int(request.route("id", default="0"))
        content = request.form("content", default="").strip()
        if not content:
            return Response.text("Le contenu est obligatoire.", status=422)
        execute(UPDATE_ONE, (content, record_id))
        return BaseController.render(
            "note/edit.html",
            request=request,
            context={
                "note": {"id": record_id, "content": content},
                "csrf_token": BaseController.csrf_token(request),
                "updated": True,
            },
        )
Élément Rôle
edit Pré-remplit le formulaire : lit la valeur courante avec fetch_one, renvoie un 404 si l'id est inconnu.
update Valide d'abord (refus 422 si vide), puis execute(UPDATE_ONE, …) : l'id et le contenu sont des paramètres liés.
request.route("id") L'id vient de la route, pas de la query string.
Vues

Créez la vue mvc/views/note/edit.html :

<!-- mvc/views/note/edit.html -->
{% extends "note/_layout.html" %}

{% block title %}Modifier la note #{{ note.id }}{% endblock %}

{% block content %}
<h1>Modifier la note #{{ note.id }}</h1>

{% if updated %}<p><strong>Note mise à jour.</strong></p>{% endif %}

<form method="post" action="/note/update/{{ note.id }}">
    <input type="hidden" name="csrf_token" value="{{ csrf_token }}">
    <input type="text" name="content" value="{{ note.content }}">
    <button type="submit">Enregistrer</button>
</form>

<p><a href="/note">Retour à la liste</a></p>
{% endblock %}

Ajoutez aussi un lien « éditer » sur chaque note de la liste, dans mvc/views/note/index.html :

<li>#{{ note.id }} : {{ note.content }}
    <a href="/note/edit/{{ note.id }}">éditer</a></li>

Le <form> est en method="post" et embarque le jeton CSRF : toute écriture est protégée.

Routes

Déclarez les deux routes dans mvc/routes.py :

# mvc/routes.py
from core.http.router import Router
from mvc.controllers.home_controller import HomeController
from mvc.controllers.note_controller import NoteController

router = Router()

with router.group("", public=True) as public:
    public.add("GET",  "/", HomeController.index, name="home-index")
    public.add("GET",  "/note", NoteController.index, name="note-index")
    public.add("GET",  "/note/edit/{id}", NoteController.edit, name="note-edit")
    public.add("POST", "/note/update/{id}", NoteController.update, name="note-update")
Tests
URL Résultat
https://localhost:8000/note la liste, avec un lien « éditer » par note
Éditer une note, changer le texte, Enregistrer « Note mise à jour. »
Soumettre vide Le contenu est obligatoire. (statut 422)
À retenir
  • Modifier, c'est pré-remplir (fetch_one) plus UPDATE … WHERE id = ? paramétré.
  • L'id vient de la route (request.route("id")).
  • Toute écriture est en POST avec CSRF.
Astuces

Les appels session (_start_session, set_session_cookie) reviennent à chaque écriture protégée.
Si vous voulez simplifier, vous pouvez les regrouper dans une classe façade de votre application, sous mvc/helpers/.

Forge ne l'ajoute pas au framework : le noyau reste minimal et explicite, l'ergonomie est à votre main.
Un parcours dédié vous montre comment construire ces façades pas à pas (Session, Cookies, Flash) : Construire vos façades helper.

Au palier suivant, nous ajoutons la suppression d'une note.

Continuer avec Supprimer un enregistrement