Binnerto ©, un système de gestion de bases de…
Il y a quelque mois de cela, nous vous avions laissé bien pantois. En effet, sur le site Binnerto©, un site très qualitatif de produits geek, il était jusque-là impossible de commander le moindre petit produit, et nous en sommes grandement désolés.
Mais aujourd’hui est un jour nouveau, nous venons de sortir du sable, enfin !
Vous allez désormais (dans un temps prochain (soumis à condition et à l’acceptation du crédit par votre banque)) pouvoir acheter (dans la limite des stocks disponibles et des vendeurs disponibles pour réaliser les commandes) tous les produits vendus par notre site dès maintenant grâce à un SGBDR !
Quelques petits rappels utiles et informations nécessaires à la compréhension
Vous vous demandez bien qu’est-ce qu’un SGBDR et à quoi cela peut bien servir pour commander votre produit préféré (et fort bien utile). Et bien pour une fois cette acronyme est en français (ou DBRMS dans la langue de Shakespeare), il signifie littéralement Système de Gestion de Bases de Données Relationnelles. Euuuuh quèsaco ? En fait c’est un logiciel qui permet de mettre en relation différents fichiers contenant de nombreuses données. Voyons à quoi cela ressemble dans notre cas, cela apportera plus de clarté.
Imaginons que vous vous appeliez Antonin Kroyable et que vous ayez commandé une Souris révolutionnaire pour votre tout nouvel appareil à la poire le 14 février 2022. Et bien le SGBDR ; nommons le affectueusement S&Co pour la suite pour plus de simplicité et de proximité, permet de lier trois fichiers pour les unifier. Dans un fichier vous trouverez vos informations personnelles, ce sera le fichier « client », dans un second, vous aurez toutes les informations sur cette souris, c’est-à-dire dans le fichier « produit ». Et pour la commande que vous avez faites ? Et bien elle se trouve dans le fichier « commande » évidemment. Tout le travail du S&Co et donc de lier ces trois fichiers pour avoir les informations les plus utiles pour chaque besoin, c’est-à-dire pour vous clients, et pour nous gestionnaires du site et responsables des stocks et livraisons produits, sans avoir des fichiers de 3 Km de long avec toutes les informations, dont certaines inutiles à certains mais utiles à d’autres.
Rentrons maintenant dans le cœur de la bête pour mieux l’appréhender.
Il faut savoir que ce S&Co, étant user-friendly, a une interface utilisateur (ou UI en anglais) graphique, permettant une meilleure expérience utilisateur (ou UX encore une fois en anglais), en limitant de perdre ce dernier, qui n’a pas forcément les connaissances nécessaires pour affronter la peur d’un terminal sombre et peu ragoûtant. Il est au moins rassuré, mais cela nous complexifie bien la tâche, comme nous allons le voir.
Dans cette quête de la rose-amère de la perfection du design, ce S&Co ne fera que pâle figure car étant codé extrêmement en Python avec comme interface graphique Tkinter, les possibilités de design sont amoindries comparé à d’autres langages mieux prévus pour les interfaces graphiques. Nonobstant, vous avez quand-même un produit a minima fini.
Il y aura donc, vous l’aurez compris deux parties majeures pour appréhender ce S&Co, la partie graphique, en Tkinter, qui n’est que la rustine de ce qui fait tourner la machine, ce qui est à l’intérieur, c’est-à-dire les fonctions mères pour mettre en relation SQL (le langage qui permet de parler aux tables de données ; le charmeur de données), et Python.
Le vif du sujet SQL-Python
Sans plus de divagations, voyons d’où tout est parti : SQLite. En effet, il est impossible de discuter directement avec les données en Python, ou alors cela serait très complexe.
Pour plus de facilité, on utilise à la place un autre langage, le SQL (qui est d’une facilitée incroyable en comparaison). Mais pour lier un programme Python avec du SQL il faut une bibliothèque, et c’est la que SQLite vient nous sauver ! En effet on peut rentrer des commandes SQL dans une fonction Python et avoir tous les bénéfices du SQL en ayant les retours en Python pour être réutilisés, c’est presque magique !
Réalisation des trois bases de données dans un fichier CSV
Pour commencer avec des données à chaque démarrage sans partir de rien, nous avons créé 3 fichiers CSV (où les éléments sont séparés par des virgules) que l’on récupère ensuite pour l’importer (on verra cela plus tard). Donc, comme on l’a dit plus tôt, 3 tables seront liées, il y aura donc 3 fichiers CSV qui contiendront quelques éléments pour chaque table (environ une dizaine).
Tout d’abord, il y a la table PRODUITS, dans le fichiers produits.csv. Dans cette table on présente comme suit les principales caractéristiques du produits :
id_produit | nom_produit | prix | stock |
6 | Binnerto en acier | 0.00 | 1 |
Toutes les données sont présentées comme cela sauf, que d’une colonne à l’autre, c’est un virgule qui les sépare. L’id_produit est un identifiant unique permettant à coup sur de retrouver le produit sans risque de se tromper (par exemple si 2 produits ont un nom identique).
Il en est de même pour la table CLIENTS sur le fichier clients.csv :
id_client | nom | prenom | adresse_mail | adresse | date_naissance | date_inscription | vip |
8 | Kroyable | Antonin | antonin.kroyable@lol.mdr | 13 chemin des Rippes, Salle-à-Sac | 10-03-2007 | 11-11-2011 | True |
De la même manière, id_client est l’identifiant unique qui correspond au client.
Passons maintenant à la dernière table, la plus importante, la table COMMANDES, dans le fichier commandes.py :
id_commande | id_client_commande | id_produit | date_commande | date_livraison | envoye | quantite | prix_total |
30 | 8 | 6 | 14-02-2022 | 19-02-2022 | True | 3 | 0.00 |
Encore une fois, id_commande est l’identifiant unique de la commande mais, il y a une petite subtilité encore. En effet, on va lier les deux autres tables à celle-ci avec 2 attributs : id_client_commande et id_produit, qui correspondent respectivement à la ligne de chaque table. Ainsi, avec une moindre écriture, on arrive à avoir toutes les informations pertinentes sur une seule ligne !
CRUD, la clé de voûte du projet
CRUD, de l’anglais Create, Read, Update, Delete, a été notre expression clé durant ce projet, notre guide suprême, car pour un S&Co, cela les 4 points les plus importants. Il faut pouvoir Créer des tables et des éléments dans ces tables, les Lire, les Mettre-à-Jour, mais aussi les supprimer. Voyons comment cela a pu être réalisé en Python/SQL.
1. Create
Deux choses sont à créer dans ce projet, les tables, que ce soit PRODUITS, CLIENTS ou encore COMMANDES, mais aussi les éléments qui composent ces tables, les lignes si vous vous rappelez de nos tableaux de tout-à-l’heure.
Pour créer une table, rien de plus simple comme on le voit ici avec la création de la table PRODUITS :
def create_table_produits(): curseur.execute("CREATE TABLE IF NOT EXISTS PRODUITS(id_produit INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE, nom_produit TEXT, prix NUMERIC, stock INTEGER)") connection.commit() #On envoie la modif dans la base de données
Le SQL est ce qui est dans la méthode .execute()
Cette fonction permet donc de créer la table PRODUITS, en vérifiant bien sûr qu’elle n’existe pas pour éviter les erreurs. Si vous vous rappelez de l’id_produit de tout à l’heure, qui permet de retrouver avec certitude un produit, et bien en SQL, c’est une PRIMARY KEY, soit une clé primaire ; une sorte de numéro d’identité du produit (qui est donc unique).
Mais on peut aussi avoir besoin de créer un enregistrement, comme suit :
def add_produits(produits_ajout): curseur.executemany("INSERT INTO PRODUITS(nom_produit, prix, stock) VALUES (?,?,?)", produits_ajout) make()
En vérifiant bien sûr que le tuple en entrée soit composé de 3 éléments.
2. Read
Pour lire, ça se corse, on peut vouloir lire tout ou partie, il a donc fallu faire des choix en réfléchissant à chaque fois au besoin le plus utile de lecture.
Ainsi on peut tout lire comme ici :
def select_all_produits(): curseur.execute("SELECT * FROM PRODUITS") return [("id_produit", "nom_produit", "prix", "stock")] + curseur.fetchall()
On retourne en plus les noms des attributs afin de pouvoir les afficher comme il faut dans le tableau en Tkinter
Mais aussi comme cela :
def select_all_commandes(): curseur.execute("SELECT nom, prenom, nom_produit, date_commande, date_livraison, envoye, quantite FROM COMMANDES AS co INNER JOIN PRODUITS AS p ON co.id_produit = p.id_produit LEFT JOIN CLIENTS AS cl ON co.id_client_commande = cl.id_client") return [("nom", "prenom", "nom_produit", "date_commande", "date_livraison", "envoye", "quantite")] + curseur.fetchall()
Ici, on fait une double jointure pour afficher à la place de l’id_client et de l’id_produit, le nom du client et du produit pour plus de clarté.
En jouant avec le SQL, il y a aussi des fonctions plus spécifiques comme celle qui suit permettant d’afficher toutes les commandes d’un même client :
def toutes_commandes_clients(id_client): curseur.execute("SELECT nom, prenom, nom_produit, date_commande, date_livraison, envoye, quantite FROM COMMANDES AS co INNER JOIN PRODUITS AS p ON co.id_produit = p.id_produit LEFT JOIN CLIENTS AS cl ON co.id_client_commande = cl.id_client WHERE id_client_commande=?",(id_client,)) return [("nom", "prenom", "nom_produit", "date_commande", "date_livraison", "envoye", "quantite")] + curseur.fetchall()
3. Update
Pour mettre à jour, nous nous sommes demandé ce que nous aurions le plus besoin de mettre à jour dans chaque enregistrement de chaque table, et pour cela nous avons pour chaque table créé une fonction update qui change uniquement l’élément qui a le plus besoin d’être changé :
def update_commandes(id_commande, envoye: bool): curseur.execute("UPDATE COMMANDES SET envoye=? WHERE id_commande=?", (envoye, id_commande)) make()
On change ici l’état de la commande avec un Booléen (Vrai/Faux), car c’est la commande que l’on est le plus amenés à réaliser.
4. Delete
Pour la suppression, afin d’être sûr de ne pas faire d’erreurs, cette dernière se base uniquement sur l’id unique de chaque enregistrement dans une table.
Ainsi :
def delete_clients(id_client): curseur.execute("SELECT * FROM COMMANDES WHERE id_client_commande=?",(id_client,)) temp = curseur.fetchall() if str(temp) == "[]": curseur.execute("DELETE FROM CLIENTS WHERE id_client=?", (id_client,)) make()
Ici, on supprime un produit en fonction de son id_produit, tout en vérifiant qu’il n’est pas utilisé dans la table commande, sinon cela pourrait poser problème.
Un affichage graphique : Tkinter
CComme dit plutôt, il faut bien interagir avec toutes ces bases de données si complexes (qu’on appellera BDD), et ça avec une interface graphique en Tkinter. Après avoir réfléchi à plusieurs croquis de ce que pourrait être l’UI (dont on vous ne donnera pas d’exemple pour ne pas nous faire voler nos maquettes), on a réussi à trouver l’UI la plus simple pour nos futurs employés, qui devront se servir de cette interface pour gérer les commandes, pendant que nous serons à Ibiza en train d’empocher un maximum d’argent.
Tous les produits de chez Binnerto en un clic
N’oublions pas le maître-mot de tout ce que l’on peut produire : LA FACILITÉ ! Il est donc inutile lorsqu’on veut sélectionner un produit d’écrire chaque lettre de ce dernier (sans oublier les potentielles erreurs de frappes ou maladresses). Nous avons donc opter pour une liste des produits qu’il suffit de sélectionner. Avec Tkinter, l’outil se nomme « Combobox » et c’est bien pratique.
from tkinter import * from tkinter import ttk def action(recup): #on appelle la fonction qui lie Python et SQL pour interagir avec la TABLE listeproduits = ["Parpaing externe", "Clé USHellB", "Clavier Pasteur STL", "Voiture à Hydrogène", "CPU Nucléaire Leif", "Bouton d'allumage automatique", "Binnerto en acier", "Souris révolutionnaire"] choix_prod = Label(racine, text = "Enlever Stock :") choix_prod.place(x=300, y=130) aff_prod = ttk.Combobox(racine, state = "readonly", values = listeproduits) aff_prod.bind("<<ComboboxSelected>>", action) aff_prod.place(x=265, y=170)
Comme une bonne nouvelle n’arrive jamais seule, certes l’outil « Combobox » est parfait pour ce que nous voulons faire, mais il n’est pas inclus lors de l’importation de Tkinter *. Il faut donc le rajouter à part, au péril d’un peu plus de mémoire.
C’est bien beau de pouvoir sélectionner un produit dans une liste, mais il faut bien le récupérer pour pouvoir l’exploiter un peu. Heureusement, la méthode « .get() » permet de récupérer le choix fait par l’utilisateur, et à l’aide de la fonction ci-dessus qui affiche ce choix, on est sûr d’avoir bien récupéré le produit. Maintenant que l’on peut sélectionner un produit, il ne reste plus qu’à décider s’il faut le retirer du stock en cas de commande, ou l’ajouter en cas de fabrication (même si ce dernier cas n’arrive jamais 🤫).
Un accès aux commandes plus pratique
Nous suivons avec soin chaque commande pour la meilleure satisfaction de nos clients. C’est pour cela qu’il faut savoir à l’aide du numéro de commande : les produits qu’elle contient, le nom et prénom du client, le jour de l’envoi, si le colis a été reçu, etc…
Il faut donc qu’à l’aide de « id_commande », on trouve toutes ces informations, et cela, de façon la plus lisible possible… et pourquoi pas un tableau ?! Mais il faut d’abord récupérer le numéro d’une commande saisit dans un input :
def num_commande(event): n_fen = Toplevel(racine) t = Tableau(n_fen, select_client_specifique(int(entree.get()))) #on ouvre une nouvelle fenêtre avec le tableau représentant les données def valide(test): return test.isdigit() com_liv = Label(racine, text = "Afficher la commande d'un client (rentrez son ID)") com_liv.place(x=900, y=130) choix_com = racine.register(valide) value = StringVar(racine) entree = Entry(racine, textvariable = value, width = 30, validate = 'key', validatecommand = (choix_com,'%S')) entree.place(x=865, y=170) entree.bind("<Return>", num_commande)
Comme pour les produits, on récupère le numéro de la commande à l’aide de la méthode « .get() », mais cette fois-ci, uniquement lorsque nous validerons en appuyant sur la touche « Entrée » du clavier. Mais imaginons que lorsque notre employé utilise cette interface, il s’endorme sur le clavier ! Malheur ! Il faut redoubler de sécurité. C’est pour cela que dans cet input, on peut seulement y renseigner des chiffres, et cela, grâce à la fonction « valide » qui vérifie à chaque saisie si celui la est un chiffre.
Le Tkinter a pu grandement avancer grâce au tutoriel d’Aël D. sur Tkinter
Vous trouverez ci-dessous le projet, qui est en constante évolution :
Cet article a été écrit par des élèves du lycée Louis Pasteur.
Ils étudiaient alors la spécialité NSI, en classe de terminale.
Promotion 2021 – 2022