Étape 3 sur 4 · environ 30 min

Créez une note, puis retrouvez-la dans Odoo

Le formulaire Vue envoie seulement un titre et un contenu. Après la réponse de création, le client relit la liste. Vous vérifierez ensuite le même enregistrement dans l’interface construite au jour 2.

Travail démontré dans le parcours principal

Le parcours porte sur une seule ressource : les enregistrements training.note. Le client Vite et Vue 3 utilise GET et POST sur /training/api/notes. Chaque note exposée contient uniquement id, title et body.

1. Relisez la fonction ajoutée dans src/api.js

Le navigateur appelle toujours /odoo-api/training/api/notes. La fonction createNote(), copiée à l’étape précédente, choisit POST, annonce un corps JSON et sérialise exactement les deux valeurs autorisées.

day4-notes-client/src/api.js · fonction ajoutée à l’étape 2
export async function createNote({ title, body }) {
  const response = await fetch(NOTES_API_URL, {
    method: "POST",
    headers: {
      Accept: "application/json",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ title, body }),
  });

  const payload = await readResponse(response);
  if (!isNote(payload?.note)) {
    throw new Error("La réponse de création ne contient pas la note attendue.");
  }
  return payload.note;
}

Le corps envoyé a la forme {"title": "…", "body": "…"}. Il ne fournit aucune identité. Vite ajoute la clé au moment de relayer l’appel, puis Odoo retrouve son propriétaire, contrôle ses droits de création et utilise l’ORM.

Une création acceptée renvoie le statut 201 Created et un objet de cette forme. L’identifiant dépend de votre base.

Exemple de réponse de création
{
  "note": {
    "id": 42,
    "title": "Note créée depuis Vue",
    "body": "Cette note passe par l'API du jour 4."
  }
}

2. Ajoutez la création dans App.vue

Dans le bloc <script setup>, remplacez les deux lignes d’import par celles-ci :

day4-notes-client/src/App.vue · imports
import { onMounted, reactive, ref } from "vue";
import { createNote, fetchNotes } from "./api.js";

Après const error = ref("");, ajoutez l’état du formulaire et de son envoi :

day4-notes-client/src/App.vue · état de création
const saving = ref(false);
const form = reactive({ title: "", body: "" });

Placez ensuite la fonction suivante entre loadNotes() et onMounted(loadNotes). Elle retire les espaces autour du titre et refuse un titre vide avant tout appel. Pendant l’envoi, saving vaut true. Après une réussite, elle vide le formulaire, puis attend loadNotes() pour afficher l’état enregistré dans Odoo.

day4-notes-client/src/App.vue · fonction à ajouter
async function submitNote() {
  const title = form.title.trim();
  if (!title) {
    error.value = "Saisissez un titre avant d'envoyer la note.";
    return;
  }

  saving.value = true;
  error.value = "";
  try {
    await createNote({ title, body: form.body });
    form.title = "";
    form.body = "";
    await loadNotes();
  } catch (requestError) {
    error.value = requestError.message;
  } finally {
    saving.value = false;
  }
}

Dans le bloc <template>, insérez maintenant ce formulaire juste après </header> et avant le message d’erreur. Le gabarit relie les deux champs à l’objet réactif form. @submit.prevent appelle la fonction sans recharger la page. Le bouton devient indisponible pendant l’attente et rend l’état d’envoi visible.

day4-notes-client/src/App.vue · formulaire à ajouter
    <section class="panel" aria-labelledby="new-note-title">
      <h2 id="new-note-title">Ajouter une note</h2>
      <form @submit.prevent="submitNote">
        <label for="title">Titre</label>
        <input id="title" v-model="form.title" name="title" autocomplete="off" required>

        <label for="body">Contenu</label>
        <textarea id="body" v-model="form.body" name="body" rows="4"></textarea>

        <button type="submit" :disabled="saving">
          {{ saving ? "Envoi…" : "Créer dans Odoo" }}
        </button>
      </form>
    </section>
Deux validations complémentaires

Le contrôle dans Vue améliore le retour immédiat, mais il ne protège pas le point d’accès : un autre client peut appeler la route. Le contrôleur Odoo doit donc continuer à valider le JSON reçu avant de créer l’enregistrement.

3. Créez une seule note de contrôle

Enregistrez App.vue. Avec Odoo et Vite toujours actifs, ouvrez http://127.0.0.1:5173. Remplissez le formulaire avec ces valeurs, puis choisissez une seule fois Créer dans Odoo.

  • Titre : Note créée depuis Vue
  • Contenu : Cette note passe par l'API du jour 4.

À observer : le bouton affiche brièvement Envoi…. La nouvelle note apparaît ensuite dans la liste relue depuis Odoo et le nombre annoncé par l’API augmente d’une unité.

Dans l’onglet Réseau des outils de développement, ouvrez la requête POST vers /odoo-api/training/api/notes et vérifiez :

  • le corps de la requête contient uniquement title et body ;
  • la requête partie du navigateur ne contient pas la clé d’API ;
  • le statut de la réponse est 201 ;
  • la réponse contient note, puis seulement id, title et body dans cet objet.

4. Retrouvez le même enregistrement dans l’interface du jour 2

  1. Dans Odoo, ouvrez Notes de formation → Notes.
  2. Actualisez la liste si elle était déjà ouverte.
  3. Ouvrez Note créée depuis Vue.
  4. Comparez les champs Titre et Contenu avec la réponse POST et la carte affichée dans Vue.

Résultat attendu : les deux interfaces montrent le même enregistrement training.note. Vue passe par l’API HTTP ; la vue standard du jour 2 passe par le client web Odoo. Le modèle, l’ORM, les ACL, les éventuelles règles sur les enregistrements et la ligne PostgreSQL restent communs.

Le composant Owl du jour 2 s’exécutait à l’intérieur du client web Odoo et appelait son service ORM. Cette application Vue est un client local séparé. Cette différence change le trajet de la requête, pas les droits que l’ORM doit appliquer à l’enregistrement final.

5. Le périmètre réellement réalisé

  • Une ressource fixe, training.note, est disponible sur un chemin explicite.
  • GET renvoie {notes, count} ; chaque note contient uniquement id, title et body.
  • POST accepte seulement {title, body} et une réussite renvoie {note: {id, title, body}}.
  • Vue 3 gère l’état, le formulaire et l’affichage ; Vite fournit le serveur et le relais de développement local.
  • La clé temporaire reste dans .env.local et dans le processus Vite, jamais dans le code source du navigateur.

Extension facultative — concevez trois ressources explicites

Cette extension n’est pas déjà réalisée

Le parcours principal s’arrête à l’API des notes. La suite est un exercice de conception facultatif. Ne présentez pas ses routes comme disponibles tant que vous ne les avez pas ajoutées et testées dans Odoo.

Préparez un contrat distinct pour chacun des trois cas ci-dessous, avec un GET de liste et un POST de création. Chaque contrat doit fixer la méthode, le chemin, les champs d’entrée, la forme de la réussite et les statuts d’erreur. Gardez une route et une sélection de champs propres à chaque ressource.

CasModèle et champs connusChemin expliciteÉtat
Notes d’originetraining.note · id, title, body/training/api/notesDéjà démontré avec GET et POST
Formations eLearning du jour 3elearning.module · name, description/training/api/modulesÀ concevoir, puis à implémenter si le temps le permet
Catégories de notes du jour 2training.note.category · name/training/api/note-categoriesÀ concevoir, puis à implémenter si le temps le permet

Pour les formations, limitez la création à name et description. Pour les catégories, limitez-la à name. Pour les notes, conservez title et body. Si vos réponses ont besoin d’un identifiant, utilisez l’id normal de l’enregistrement ; n’inventez pas d’autres champs.

Ne construisez pas un point d’accès auquel le client fournirait un nom de modèle ou une liste de champs. Le contrôleur doit choisir lui-même le modèle et les seules valeurs exposées. Dans chaque cas, utilisez l’environnement de la requête, laissez les ACL et les règles sur les enregistrements contrôler l’opération et n’ajoutez pas sudo().

Critères de réussite de l’extension
  • Les trois ressources ont des chemins et des contrats distincts.
  • Les champs restent limités à ceux démontrés aux jours 2 et 3.
  • Les créations n’envoient aucune identité choisie par le navigateur.
  • Un appel sans clé ou sans droit reste refusé par Odoo.
  • Vous n’ajoutez ni nouvelle bibliothèque ni sujet de déploiement.

6. Fermez l’exercice local

Quand vous avez terminé, arrêtez Vite avec Ctrl + C, révoquez la clé temporaire dans Odoo, puis supprimez .env.local avant toute archive. Gardez les sources et .env.example.

Résultat de la section

Vous avez utilisé une application Vite et Vue 3 pour lire de vraies notes Odoo et en créer une par un contrat POST limité. Vous avez retrouvé le même enregistrement dans l’interface du jour 2 sans exposer la clé au code du navigateur.