Étiquette : SQL

Projets

NsCinéma – La base de données

Trouver le film idéal à regarder ? C’est ce que vous proposera cette base de donnée de films utilisant tkinter.
Cette collection comportant des films de tout genre regroupe de nombreux films en les notant, très pratique pour dénicher les meilleurs films.

Le thème

Nous avons choisi le cinéma comme thème pour notre base de données car nous avons tous les deux cette passion en commun. En effet, en plus du fait que nous aimons le cinéma, l’idée de choisir des films pour constituer notre base de données semble correspondre aux consignes et nous a assez vite donné des idées de fonctionnalités, notamment avec les filtres de recherche que nous avons mis en place.

Le code

Tout d’abord nous avons importé toutes les bibliothèques nécessaires au bon fonctionnement de la base de donnée.

from tkinter import *
import tkinter as tk
import sqlite3
from tkinter import messagebox

Ici Tkinter va permettre d’avoir un rendu graphique de la base de données, le tutoriel présent sur le site nsi.xyz nous a fortement aidé (tutoriel).

Enfin Sqlite3 permet de nous connecter a la base de donné (DBbrowser)

Page d’accueil

Pour la page d’accueil, nous avons donc décidé de rester simple. En effet, sur cette page, on y trouve le nom de notre projet, « NsCinéma ». Ensuite, il y a deux boutons : tout d’abord, un bouton qui permet de consulter la base de données sans la modifier, avec un système de filtres et de défilement. Ensuite, un deuxième bouton permet de modifier la base de données, ce qui inclut la création d’une table Film, la suppression de la table, l’ajout de films, ainsi que la possibilité de modifier un film en cas d’erreur lors de l’ajout.

Ainsi, afin que les boutons puissent fonctionner correctement avec l’ouverture d’une nouvelle page, il était nécessaire d’implémenter une fonction permettant de créer la page « Modifier la base de donnée »

Fonction create permettant d’acceder a la page modifier la base de donnée :

def create():
    glodef create():
    global nouvelle_fenetre
    nouvelle_fenetre = tk.Toplevel(screen)
    nouvelle_fenetre.geometry("1080x720")
    nouvelle_fenetre.config(bg="#0e101c")
    
    connection = sqlite3.connect('base_films.db')
    curseur = connection.cursor()
    
    def retour():
        nouvelle_fenetre.destroy()

    #titre
    titre = Label(nouvelle_fenetre, text="Mise a jour de la base de donnée", font=("Consolas",40) ,bg="#0e101c", fg="#b3b3b3")
    titre.pack(pady=50)
    
    #creer la table 
    bouton_creer_table = Button(nouvelle_fenetre, text="Créer Table Film", font=("Consolas",13), bg="#1d1e2e", fg="white", command=create_table, width=60, height=3)
    bouton_creer_table.place(x=250, y=300)

    #Bouton supprimer 
    bouton_supprimer_table = Button(nouvelle_fenetre, text="Supprimer Table Film", font=("Consolas",13), bg="#1d1e2e", fg="white", command=supprimer_table, width=60, height=3)
    bouton_supprimer_table.place(x=250, y=400)

    #bouton ajouter
    bouton_ajouter_film = Button(nouvelle_fenetre, text="Ajouter un film", font=("Consolas",13), bg="#1d1e2e", fg="white", command=page_ajouter_film, width=60, height=3)
    bouton_ajouter_film.place(x=250, y=500)
    
    #bouton modifier
    bouton_ajouter_film = Button(nouvelle_fenetre, text="Modifier la table", font=("Consolas",13), bg="#1d1e2e", fg="white", command=update, width=60, height=3)
    bouton_ajouter_film.place(x=250, y=600)
    
    bouton_retour = Button(nouvelle_fenetre, text="Retour", font=("Consolas",13), bg="#1d1e2e", fg="white", command=retour, width=20, height=2)
    bouton_retour.place(x=850, y=650)
    
    
    
    connection.commit()
    curseur.close()
    connection.close()

Dans cette fonction, nous pouvons observer la présence de la fonction ‘retour’ permettant de revenir en arrière en supprimant la page actuelle.

Bouton :

bouton1 = Button(screen, text="Consulter la base de donnée ", font=("Consolas",13), bg="#272936", fg="white", command= create, width=60, height=3)
bouton1.place(x=250, y=300)

bouton2 = Button(screen, text="Mettre a jour la base de donnée ", font=("Consolas",13), bg="#272936", fg="white", command= create, width=60, height=3)
bouton2.place(x=250, y =400)

Consultation de la base de donnée

Passons maintenons au code permettant de consulter cette base donnée. On créée donc une nouvelle fenêtre constituée de deux parties : une pour appliquer des filtres sur les films et une pour voir les films.

Comme on peut le constater dans le screen ci-dessous.

Vous constatez donc une boîte où l’on peut consulter les films de la base, et au-dessus de celle-ci, un bloc comportant un menu déroulant pour les filtres.

On pourra noter l’utilisation d’un bouton retour pour revenir à la page d’accueil et d’un bouton actualise qui nous servira plus tard pour ajouter/supprimer des films.

Passons plus en détail le code.

nouvelle_fenetre = tk.Tk()
    nouvelle_fenetre.title("NsCinema")
    nouvelle_fenetre.geometry("1080x720")
    nouvelle_fenetre.config(bg="#0e101c")

main_frame = Frame(nouvelle_fenetre, bg="#0e101c")
main_frame.place(x=80, y=300, width=920, height=420)

canvas = Canvas(main_frame, bg="#0e101c")
canvas.pack(side=LEFT, fill=BOTH, expand=YES)

scrollbar = Scrollbar(main_frame, command=canvas.yview)
scrollbar.pack(side=RIGHT, fill=Y)

canvas.configure(yscrollcommand=scrollbar.set)

boitefilm = Frame(canvas, bg="#1d1e2e")
canvas.create_window((0, 0), window=boitefilm, anchor='nw')
    
bouton_retour = Button(nouvelle_fenetre, text="Retour", font=("Consolas",13), bg="#1d1e2e", fg="white", command=retour, width=20, height=2)
bouton_retour.place(x=80, y=120)
    
bouton_actualise = Button(nouvelle_fenetre, text="Actualise", font=("Consolas",13), bg="#1d1e2e", fg="white", command=actualise, width=20, height=2)
bouton_actualise.place(x=800, y=120)
    

boitefilm correspond à la fenêtre (c’est une frame) où l’on, comme on l’à déjà dit, consulte les films. Elle est dans un canevas (ici canvas) qui lui va regrouper tous les éléments graphiques dont boitefilm et notamment la scrollbar qui va permettre de défiler l’axe des ordonnées de canvas : command=canvas.yview.

def etoile(n):
        c = n
        note = ""
        while c != 0:
            note += "★"
            c -= 1
            
        while (5-n) != 0:
            note += "☆"
            n += 1
        return note

    def titre():
        t_nom = Label(boitefilm, text="Nom", font=("Consolas", 14), bg="#1d1e2e", fg="black")
        t_genre = Label(boitefilm, text="Genre", font=("Consolas", 14), bg="#1d1e2e", fg="black")
        t_realisateur = Label(boitefilm, text="Réalisateur", font=("Consolas", 14), bg="#1d1e2e", fg="black")
        t_annee_pub = Label(boitefilm, text="Année", font=("Consolas", 14), bg="#1d1e2e", fg="black")
        t_note = Label(boitefilm, text="Note", font=("Consolas", 13), bg="#1d1e2e", fg="black")

        t_nom.grid(row=0, column=0, sticky=W, pady=21, padx=20)
        t_genre.grid(row=0, column=1, sticky=W, pady=21, padx=5)
        t_realisateur.grid(row=0, column=2, sticky=W, pady=21, padx=5)
        t_annee_pub.grid(row=0, column=3, sticky=W, pady=21, padx=10)
        t_note.grid(row=0, column=4, sticky=W, pady=21, padx=12)
    
            
    def note(n):
        connection = sqlite3.connect('base_films.db')
        curseur = connection.cursor()

        curseur.execute("SELECT * FROM Film WHERE note=?", (n,))
        films = curseur.fetchall()

        connection.commit()
        curseur.close()
        connection.close()

        return films

Ici nous avons trois fonctions qui ont des tâches spécifiques :

  • etoile(n) : c’est une fonction qui va retourner, en fonction du paramètre n, une chaîne de caractère avec n étoiles « ★ » et (5-n) étoiles « ☆ ». Ce qui nous donnes au final un note sur 5 représentée avec des étoiles.
  • titre() : cette fonction va tout simplement permettre l’affichage des titres (avec l’objet Label de tkinter qui permet d’afficher du texte) à la première ligne de la grille avec le paramètre row=0.
  • note() : C’est une fonction qui se connecte à une notre de données contenant les informations sur les films. Elle récupère tous les films ayant une note égale à n et les renvoie. Ici, films = curseur.fetchall() permet de récupérer dans la variable films la requête SQL.

Passons maitenant à la fonction la plus importante de cette partie : ajoute_film()

    
    def ajoute_film(nom, genre, realisateur, annee_pub, note):
        global row_compteur
        connection = sqlite3.connect('base_films.db')
        curseur = connection.cursor()

        truc = (nom, genre, realisateur, annee_pub, note)
        curseur.execute('''CREATE TABLE IF NOT EXISTS Film (
                        id_film INTEGER PRIMARY KEY AUTOINCREMENT,
                        nom TEXT,
                        genre TEXT,
                        realisateur TEXT,
                        annee_pub INTEGER,
                        note REAL
                    )''')
        

        curseur.execute("INSERT INTO Film (nom, genre, realisateur, annee_pub, note) VALUES (?,?,?,?,?)", truc)
        
        

        connection.commit()
        curseur.close()
        connection.close()

        b_nom = Label(boitefilm, text="%s" %(nom), font=("Consolas", 14), bg="#1d1e2e", fg="white")
        b_genre = Label(boitefilm, text="%s" %(genre), font=("Consolas", 14), bg="#1d1e2e", fg="white")
        b_realisateur = Label(boitefilm, text="%s" %(realisateur), font=("Consolas", 14), bg="#1d1e2e", fg="white")
        b_annee_pub = Label(boitefilm, text="%s" %(annee_pub), font=("Consolas", 14), bg="#1d1e2e", fg="white")
        b_note = Label(boitefilm, text="%s" %(etoile(note)), font=("Consolas", 13), bg="#1d1e2e", fg="white")

        b_nom.grid(row=row_compteur, column=0, sticky=W, pady=21, padx=30)
        b_genre.grid(row=row_compteur, column=1, sticky=W, pady=21, padx=15)
        b_realisateur.grid(row=row_compteur, column=2, sticky=W, pady=21, padx=10)
        b_annee_pub.grid(row=row_compteur, column=3, sticky=W, pady=21, padx=10)
        b_note.grid(row=row_compteur, column=4, sticky=W, pady=21, padx=20)
		
        row_compteur += 1


Cette fonction, ajoute_film, ajoute les informations d’un film (nom, genre, réalisateur, année de publication, note) à une base de données SQLite. Elle crée également des objets Label pour afficher ces informations dans une grille, en utilisant la variable row_compteur (c’est une globale) pour déterminer la ligne à laquelle il faut placer ces dernières.

En somme, tel un plat de lasagnes, on ajoute les films ligne après ligne.

Nous utilisons donc cette fonction pour construire notre base de données à l’aide du bout de code suivant et d’une bonne veille boucle for i in range() :

data = [
        ("American Sniper", "Biopic", "Clint Eastwood", 2015, 4),
        ("The Fabel Man", "Biopic", "Steven Spielberg", 2022, 3),
        ("Truman Show", "Science-Fiction", "Peter Weir", 1998, 4),
        ("Prisoners", "Drame", "Denis Villeneuve", 2013, 4),
        ("Very Bad Trip 1", "Comédie", "Todd Phillips", 2009, 3),
        ("Very Bad Trip 2", "Comédie", "Todd Phillips", 2011, 3),
        ("Very Bad Trip 3", "Comédie", "Todd Phillips", 2013, 2),
        ("Ocean Eleven", "Action", "Steven Soderbergh", 2001, 3),
        ("Ocean Twelve", "Action", "Steven Soderbergh", 2004, 2),
        ("Ocean Thirteen", "Action", "Steven Soderbergh", 2007, 3),
        ("La La Land", "Drame", "Damien Chazelle", 2016, 4),
        ("Fury", "Action", "David Ayer", 2014, 3),
        ("Voyage de Chihiro", "Animation", "Hayao Miyazaki", 2001, 5),
        ("Le Château Ambulant", "Animation", "Hayao Miyazaki", 2004, 4),
        ("Goodfellas", "Drame", "Martin Scorsese", 1990, 3),
        ("A Man Called Otto", "Drame", "Marc Forster", 2022, 3),
        ("Elvis", "Biopic", "John Carpenter", 1979, 3),
        ("127 Hours", "Biopic", "Danny Boyle", 2010, 3),
        ("Ad Astra", "Science-Fiction", "James Gray", 2019, 3),
        ("Life: Origine Inconnue", "Science-Fiction", "Daniel Espinosa", 2017, 3),
        ("The Hobbit 1", "Action", "Peter Jackson", 2012, 4),
        ("The Hobbit 2", "Action", "Peter Jackson", 2013, 4),
        ("The Hobbit 3", "Action", "Peter Jackson", 2014, 4),
        ("The Lord of the Rings 1", "Action", "Peter Jackson", 2001, 4),
        ("The Lord of the Rings 2", "Action", "Peter Jackson", 2002, 4),
        ("The Lord of the Rings 3", "Action", "Peter Jackson", 2003, 5),
        ("The Imitation Game", "Biopic", "Morten Tyldum", 2014, 3),
        ("Seul au monde", "Drame", "Robert Zemeckis", 2000, 4),
        ("Nightcrawler", "Thriller", "Dan Gilroy", 2014, 4),
        ("Marriage Story", "Drame", "Noah Baumbach", 2019, 3),
        ("First Man", "Biopic", "Damien Chazelle", 2018, 3),
        ("L'Intervention", "Action", "Fred Grivois", 2019, 4),
        ("Stand by Me", "Drame", "Rob Reiner", 1986, 4),
        ("Middle Men", "Drame", "George Gallo", 2009, 3),
        ("Happiness Therapy", "Comédie", "David O. Russell", 2012, 4),
        ("The Interview", "Comédie", "Seth Rogen", 2014, 1),
        ("Joy", "Drame", "David O. Russell", 2015, 3),
        ("The Silence of the Lambs", "Thriller", "Jonathan Demme", 1991, 5),
        ("Dune : Partie 2", "Science-Fiction", "Denis Villeneuve", 2024, 4),
        ("Man on Fire", "Action", "Tony Scott", 2004, 3),
        ("Dead Poets Society", "Drame", "Peter Weir", 1989, 5),
        ("Batman & Robin", "Action", "Joel Schumacher", 1997, 2),
        ("Catwoman", "Action", "Pitof", 2004, 1),
        ("Battlefield Earth", "Science-Fiction", "Roger Christian", 2000, 1),
        ("Gigli", "Comédie", "Martin Brest", 2003, 1),
        ("The Love Guru", "Comédie", "Marco Schnabel", 2008, 2),
        ("Movie 43", "Comédie", "Various", 2013, 1),
        ("Birdemic: Shock and Terror", "Thriller", "James Nguyen", 2010, 1),
        ("The Happening", "Thriller", "Night Shyamalan", 2008, 2),
        ("The Room", "Drame", "Tommy Wiseau", 2003, 1),
        ("Troll 2", "Comédie", "Claudio Fragasso", 1990, 2),
        ("Manos: The Hands of Fate", "Thriller", "Harold P. Warren", 1966, 1)]
        
        for m in range(len(data)):
                ajoute_film(data[m][0], data[m][1], data[m][2], data[m][3], data[m][4])

Passons à l’explication du menu déroulant comportant les filtres.

On constate qu’il y a deux filtres : un par genre et un par note.

Cela est possible grâce au bout de code suivant :

boitefiltre = Frame(nouvelle_fenetre, bg="#1d1e2e")
boitefiltre.place(x=80, y=200, width=920, height=63)

m_genre= Menubutton(boitefiltre, text="Genre", relief="raised", borderwidth=2, width=75)
m_genre.grid(row=1, column=0, sticky="s")

m_note = Menubutton(boitefiltre, text="Note", relief="raised", borderwidth=2, width=75)
m_note.grid(row=1, column=1, sticky="s")

boitefiltre.grid_rowconfigure(0, weight=1)

m_filtre = Label(boitefiltre, text="Appliquer des filtres de recherche", font=("Consolas",20), bg="#1d1e2e", fg="white")
m_filtre.grid(row=0, column=0, sticky="s", columnspan=5)

menuDeroulant1 = Menu(m_genre)
menuDeroulant1.add_command(label='Action', command=lambda: filtrer_par_genre('Action'))
menuDeroulant1.add_command(label="Science-Fiction", command=lambda: filtrer_par_genre('Science fiction'))
menuDeroulant1.add_command(label="Biopic", command=lambda: filtrer_par_genre('Biopic'))
menuDeroulant1.add_command(label="Drame", command=lambda: filtrer_par_genre('Drame'))
menuDeroulant1.add_command(label="Thriller", command=lambda: filtrer_par_genre('Thriller'))
menuDeroulant1.add_command(label="Comédie", command=lambda: filtrer_par_genre('Comédie'))
m_genre.configure(menu=menuDeroulant1)

menuDeroulant2 = Menu(m_note)
menuDeroulant2.add_command(label='★☆☆☆☆', command=lambda: filtrer_par_note(1))
menuDeroulant2.add_command(label="★★☆☆☆", command=lambda: filtrer_par_note(2))
menuDeroulant2.add_command(label="★★★☆☆", command=lambda: filtrer_par_note(3))
menuDeroulant2.add_command(label="★★★★☆", command=lambda: filtrer_par_note(4))
menuDeroulant2.add_command(label="★★★★★", command=lambda: filtrer_par_note(5))

Ce bout de code créée une boîte de filtres comprenant deux menus déroulants (m_genre et m_note). Ces menus permettent de sélectionner des options de filtrage par genre de film et par note. La boîte de filtres contient également un label (m_filtre) indiquant à quoi elle sert, pour faciliter la compréhension de l’utilisateur.

Les menus déroulants sont associés à des commandes qui déclenchent des fonctions (filtrer_par_genre et filtrer_par_note) et en fonction des sélections de l’utilisateur, cela applique les filtres de recherche pour afficher les films voulues.

Modification de la base de donnée

Sur cette page nous pouvons retrouver les 5 boutons principaux :

  • Créer la table film
  • Supprimer la table film
  • Ajouter un film a la table
  • modifier la table film
  • supprimer un film

Fonction Création Table

def create_table():
    connection = sqlite3.connect('base_films.db')
    curseur = connection.cursor()
    
    curseur.execute('''CREATE TABLE IF NOT EXISTS Film (
                        id_film INTEGER PRIMARY KEY AUTOINCREMENT,
                        nom TEXT,
                        genre TEXT,
                        realisateur TEXT,
                        annee_pub INTEGER,
                        note REAL
                    )''')
     messagebox.showinfo("Creation Table", "La table a été crée.")
    
    connection.commit()
    curseur.close()
    connection.close()

Cette fonction create_table permet de créer une table dans une base de données SQLite nommée 'base_films.db‘. Elle établit une connexion avec la base de donnée 'base_films.db‘ , permettant d’accéder à la base de donnée et d’y effectuer des operations, ajouter, supprimer, mettre à jour… Ainsi la requête SQL permet de creer la table Film avec les attributs suivants : id_film, nom, genre, realisateur, annee_pub, note)

De plus un message est affiché lorsque la table est crée.

Fonction Supprimer Table

def supprimer_table():
    connection = sqlite3.connect('base_films.db')
    curseur = connection.cursor()
    
    curseur.execute("DROP TABLE IF EXISTS Film")
    messagebox.showinfo("Suppression effectuee", "La table a été supprimée.")
   
    connection.commit()
    curseur.close()
    connection.close()

Cette fonction supprimer_table permet de supprimer la table crée précédemment.

Fonction ajouter un film

Lorsque l’on souhaite ajouter un film, une nouvelle page s’ouvre.

La fonction permettant d’ouvrir cette page :

def page_ajouter_film():
    global variable, variable1, variable2, variable3, variable4  
    nouvelle_fenetre = tk.Toplevel(screen)
    nouvelle_fenetre.geometry("1080x720")
    nouvelle_fenetre.config(bg="#0e101c")
    def retour():
        nouvelle_fenetre.destroy()

    titre = Label(nouvelle_fenetre, text="Ajouter un film", font=("Consolas",40) ,bg="#0e101c", fg="#b3b3b3")
    titre.pack(pady=50)


    variable = tk.StringVar()
    variable.set("Nom du film")
    entree = Entry(nouvelle_fenetre, textvariable=variable, font=("Helvetica",15), bg="#1d1e2e", fg="#b3b3b3", width=30)
    entree.pack(padx=10, pady=10)


    variable1 = tk.StringVar()
    variable1.set("Genre du Film")
    entree1 = Entry(nouvelle_fenetre, textvariable=variable1, font=("Helvetica",15), bg="#1d1e2e", fg="#b3b3b3", width=30)
    entree1.pack(padx=10, pady=10)

    
    variable2 = tk.StringVar()
    variable2.set("Realisateur")
    entree2 = Entry(nouvelle_fenetre, textvariable=variable2, font=("Helvetica",15), bg="#1d1e2e", fg="#b3b3b3", width=30)
    entree2.pack(padx=10, pady=10)

 
    variable3 = tk.StringVar()
    variable3.set("Annee de publication")
    entree3 = Entry(nouvelle_fenetre, textvariable=variable3, font=("Helvetica",15), bg="#1d1e2e", fg="#b3b3b3", width=30)
    entree3.pack(padx=10, pady=10)

     
    variable4 = tk.StringVar()
    variable4.set("Note")
    entree4 = Entry(nouvelle_fenetre, textvariable=variable4, font=("Helvetica",15), bg="#1d1e2e", fg="#b3b3b3", width=30)
    entree4.pack(padx=10, pady=10)

   
    bouton_ajouter_film = Button(nouvelle_fenetre, text="Ajouter le film", font=("Consolas",13), bg="#1d1e2e", fg="white", command=ajouter_film, width=20, height=2)
    bouton_ajouter_film.pack(pady=20)
    
    bouton_retour = Button(nouvelle_fenetre, text="Retour", font=("Consolas",13), bg="#1d1e2e", fg="white", command=retour, width=20, height=2)
    bouton_retour.place(x=850, y=650)

Ainsi cette fonction permet d’avoir un rendu graphique explicite pour l’utilisateur. En effet cette page est composé de 5 encadrés remplie d’un champ permettant de faire savoir a l’utilisateur ou rentrer le titre du film… Le bouton ajouter film permet d’ajouter le film a la base de donnée

Afin que cette page puisse fonctionner correctement il est imperatif d’avoir une fonction executant le code SQL qui va avec.

Code permettant l’ajout du film :

def ajouter_film():
    
    global variable, variable1, variable2, variable3, variable4, data  
    nom_film = variable.get()
    genre = variable1.get()
    realisateur = variable2.get()
    annee_pub = variable3.get()
    note = variable4.get()

    connection = sqlite3.connect('base_films.db')
    curseur = connection.cursor()

    curseur.execute('''INSERT INTO Film (nom, genre, realisateur, annee_pub, note)
                        VALUES (?, ?, ?, ?, ?)''', (nom_film, genre, realisateur, annee_pub, note))

    connection.commit()
    curseur.close()
    connection.close() 

    messagebox.showinfo("Film ajoute", "Le film a ete ajoute avec succes.")

Cette fonction ajouter un film dans la base de données SQLite et informe l’utilisateur que le film a été ajouté . En effet la ligne 13 exécute une commande SQL qui insere le film dans la base de données. Les “?” sont des paramètres qui sont remplacés par les valeurs des variables spécifiées.

Fonction modifier la table

Lorsque l’on souhaite modifier la table, une nouvelle page s’ouvre.

Fonction permettant d’ouvrir la page de modification:

def update():
    global variable, variable1, variable2, variable3, variable4 
    nouvelle_fenetre = tk.Toplevel(screen)
    nouvelle_fenetre.geometry("1080x720")
    nouvelle_fenetre.config(bg="#0e101c")
    nouvelle_fenetre.title("Modifier un film")

    def retour():
        nouvelle_fenetre.destroy()

    titre = Label(nouvelle_fenetre, text="Modifier un film", font=("Consolas",40) ,bg="#0e101c", fg="#b3b3b3")
    titre.pack(pady=50)

    variable = tk.StringVar()
    variable.set("Nom du film")
    entree = Entry(nouvelle_fenetre, textvariable=variable, font=("Helvetica",15), bg="#1d1e2e", fg="#b3b3b3", width=30)
    entree.pack(padx=10, pady=10)

    variable1 = tk.StringVar()
    variable1.set("Genre du Film")
    entree1 = Entry(nouvelle_fenetre, textvariable=variable1, font=("Helvetica",15), bg="#1d1e2e", fg="#b3b3b3", width=30)
    entree1.pack(padx=10, pady=10)

    variable2 = tk.StringVar()
    variable2.set("Realisateur")
    entree2 = Entry(nouvelle_fenetre, textvariable=variable2, font=("Helvetica",15), bg="#1d1e2e", fg="#b3b3b3", width=30)
    entree2.pack(padx=10, pady=10)

    variable3 = tk.StringVar()
    variable3.set("Annee de publication")
    entree3 = Entry(nouvelle_fenetre, textvariable=variable3, font=("Helvetica",15), bg="#1d1e2e", fg="#b3b3b3", width=30)
    entree3.pack(padx=10, pady=10)

    variable4 = tk.StringVar()
    variable4.set("Note")
    entree4 = Entry(nouvelle_fenetre, textvariable=variable4, font=("Helvetica",15), bg="#1d1e2e", fg="#b3b3b3", width=30)
    entree4.pack(padx=10, pady=10)

    bouton_update_film = Button(nouvelle_fenetre, text="Mettre a jour le film", font=("Consolas",13), bg="#1d1e2e", fg="white", command=update_film, width=20, height=2)
    bouton_update_film.pack(pady=20)

    bouton_retour = Button(nouvelle_fenetre, text="Retour", font=("Consolas",13), bg="#1d1e2e", fg="white", command=retour, width=20, height=2)
    bouton_retour.place(x=850, y=650)

Ce code est semblable a celui de la creation de la page ajouter_film puisque on y retrouve les champs ou l’on doit inserer les informations necessaire.

Comme la page precedente afin que celle ci puisse fonctionner correctement il est imperatif d’avoir une fonction executant le code SQL qui va avec.

def update_film():
    nom_film = variable.get()
    genre = variable1.get()
    realisateur = variable2.get()
    annee_pub = variable3.get()
    note = variable4.get()

    connection = sqlite3.connect('base_films.db')
    curseur = connection.cursor()

    curseur.execute('''UPDATE Film SET genre=?, realisateur=?, annee_pub=?, note=? WHERE nom=?''', (genre, realisateur, annee_pub, note, nom_film))

    connection.commit()
    curseur.close()
    connection.close()

    messagebox.showinfo("Film modifié", "Le film a été modifié.")

Cette fonction met à jour les informations d’un film spécifique dans une base de données SQLite en utilisant le nom du film comme clé, et informe l’utilisateur que le film a été modifié.

Fonction Supprimer un film

Fonction permettant d’afficher cette page :

def page_supprimer_film():
    global variable_title
    nouvelle_fenetre = tk.Toplevel(screen)
    nouvelle_fenetre.geometry("1080x720")
    nouvelle_fenetre.config(bg="#0e101c")

    def retour():
        nouvelle_fenetre.destroy()

    titre = Label(nouvelle_fenetre, text="Supprimer un film", font=("Consolas", 40), bg="#0e101c", fg="#b3b3b3")
    titre.pack(pady=50)

    variable_title = tk.StringVar()
    variable_title.set("Titre du film à supprimer")
    entree_title = Entry(nouvelle_fenetre, textvariable=variable_title, font=("Helvetica", 15), bg="#1d1e2e", fg="#b3b3b3", width=30)
    entree_title.pack(padx=10, pady=10)

    bouton_supprimer_film = Button(nouvelle_fenetre, text="Supprimer le film", font=("Consolas", 13), bg="#1d1e2e", fg="white", command=supprimer_titre, width=20, height=2)
    bouton_supprimer_film.pack(pady=20)

    bouton_retour = Button(nouvelle_fenetre, text="Retour", font=("Consolas",13), bg="#1d1e2e", fg="white", command=retour, width=20, height=2)
    bouton_retour.place(x=850, y=650)

Fonction permettant de faire marcher la page :

def supprimer_titre():
    title = variable_title.get()

    connection = sqlite3.connect('base_films.db')
    cursor = connection.cursor()

    cursor.execute("SELECT id_film FROM Film WHERE nom=?", (title,))
    result = cursor.fetchone()

    if result:
        film_id = result[0]

        cursor.execute("DELETE FROM Film WHERE id_film=?", (film_id,))
        messagebox.showinfo("Film supprimé", "Le film a été supprimé avec succès.")
    else:
        messagebox.showwarning("Film introuvable", "Aucun film trouvé avec ce titre.")

    connection.commit()
    cursor.close()
    connection.close()

Cette fonction supprime un film spécifique de la base de données SQLite en utilisant le titre du film comme clé, et informe l’utilisateur que le film a été supprimé ou qu’aucun film n’a été trouvé avec le titre donné.

Bouton Retour

En effet, a la fin du projet nous avons constater que il manquait un bouton afin de revenir en arrière. Sans bouton l’utilisateur est obligé de fermer la fenêtre par la croix.

Ainsi la fonction suivante permet de fermer la page actuelle afin de revenir sur la page précédente.

def retour():
        nouvelle_fenetre.destroy()

Télécharger le projet

Vous trouverez ici le .zip du projet que vous puissiez le télécharger. PS : si vous relancer le script, il vaut mieux recréer la base de donnée à l’aide de DB Browser.

En espérant que vous trouverez un bon film.

Projets

Un site pour mettre à l’épreuve vos connaissances du…

web.snt.nsi.xyz est un projet répertoriant 10 énigmes sur-mesure accessibles pour un élève en classe de seconde. Plus globalement, ce site va vous permettre de découvrir différents aspects de l’informatique et du Web, en passant par l’histoire de l’informatique jusqu’au fonctionnement d’un site Web. Aujourd’hui vous est présenté la V2 de ce projet, une mise à jour majeure.

La V1 de ce projet

Ce projet, dopé au HTML, CSS, JavaScript, et PHP, a été lancé par Ilyas R. qui a conçu et développé l’entièreté du site, incluant les 10 énigmes, le système de chronométrage, etc. Il a également échafaudé la direction artistique en s’aidant d’une librairie CSS (également appelé «Framework CSS»). Il s’agit de PureCSS, une librairie gratuite et open-source que vous pouvez également utiliser dans vos projets. De manière générale, Ilyas a conçu la structure principale du site. Le site web.snt.nsi.xyz est né suite aux vacances de la Toussaint.

Une V2 ?

Pour les vacances d’hiver, une V2 du site a été envisagée incluant un bon nombre de fonctionnalités. Pour ce faire, Thomas S. a rejoint le projet et l’objectif principal est maintenant que le site fonctionne autour d’une base de données. Voici le cahier des charges que nous avons rédigés :

  • Système de gestion de session :
    • Créer une session (en tant que professeur notamment)
    • Rejoindre une session (en tant qu’élève notamment)
    • Observation d’une session (pour que le professeur voit quelles énigmes ont été faites)
    • Statistiques pour les sessions (pourcentages des énigmes résolues, etc.)
    • Et bien plus…
  • Système de comptes (Créer un compte / Se connecter)
  • Système pour parcourir des sessions précédemment créées
  • Système de traductions
  • Ainsi que d’autres petites fonctionnalités…

À partir de là, l’aventure et les galères vont commencer.

Structure de la base de données

Tout d’abord, avant de nous lancer directement à l’aveugle dans le développement, nous avons conçu et organisé notre base de données, en déterminant toutes les relations à créer ainsi que les attributs. Ainsi, voici la structure finale de notre base de données relationnelle.

Nous n’aurons, à notre plus grand regret, pas le temps nécessaire de développer un système de permissions élaboré et un système de gestion des traductions, ainsi dans la V2, les relations groups, permissions, et assets ne sont pas présentes.

Des briques fondamentales

Avant toute chose, il est important de concevoir des fonctions et des bouts de pages qui seront utilisés à maintes reprises. Par exemple le panneau de navigation à gauche.
Ou encore, ce qui nous intéresse le plus ici des fonctions pour manipuler la base de données. Il a fallu créer des fonctions pour créer, lire, modifier et supprimer (CRUD) des enregistrements dans des relations (tables) de notre base de données.

Créer un enregistrement

Voici la fonction que nous avons développé pour créer un enregistrement dans notre base de données :

function addRow($database, $relation, $values) {
    $attributs = getAttributs($database, $relation, 0); // Permet de récupérer la liste des attributs de la relation, hormis la clé primaire
    $attributs_query = "(".implode(", ", array_map(function($value) {
        return $value;
    }, $attributs)).")";
    $values_query = "(".implode(", ", array_map(function($value) {
        return "\"".$value."\"";
    }, $values)).")";
    $sql = "INSERT INTO $relation $attributs_query VALUES $values_query";
    $stmp = $database->prepare($sql);
    $stmp->execute();
}

La fonction prend 3 paramètres, $database, $relation, et $values, le premier indique dans quelle base de données agir (en l’occurrence, ce sera toujours la même étant donné que nous possédons uniquement une base de données), le deuxième indique la relation (table) où agir, et le dernier les valeurs à ajouter dans l’enregistrement. La fonction utilise ainsi la requête SQL INSERT INTO $relation {attributs} VALUES $values, en déterminant automatiquement les attributs de la relation où agir, il n’est pas nécessaire de rentrer en paramètre les attributs.

Lire des enregistrements

Voici la fonction que nous avons développé pour lire un ou plusieurs enregistrements dans notre base de données :

function getRows($database, $relation, $attribut, $query, $force_multiples_rows = 0) {
    $sql = "SELECT $attribut FROM $relation WHERE $query";
    $stmp = $database->prepare($sql);
    $stmp->execute();
    return rowsCount($database, $relation, $query) > 1 || $force_multiples_rows == 1 ? $stmp->fetchAll() : $stmp->fetch(PDO::FETCH_ASSOC);
}

La fonction prend 4 paramètres, $database, $relation, $attribut, et $query. De la même manière que la fonction addRow(), nous retrouvons les mêmes paramètres $database et $relation, mais ici avec 2 autres paramètres, $attribut qui représente le ou les attributs dans le ou lesquels les données doivent être collectées, ainsi que $query qui représente la ou les conditions à appliquer pour filtrer les lignes de la table de la base de données. La fonction utilise ainsi la requête SQL SELECT $attribut FROM $relation WHERE $query. Sachant que si nous ne voulons pas mettre de condition, il suffit de renseigner « 1 » dans le paramètre $query.

Mettre à jour un enregistrement

Voici la fonction que nous avons développé pour mettre à jour un enregistrement dans notre base de données :

function updateRow($database, $relation, $attributs_values, $query){
    $setCommand = "";
    $attributs = array_keys($attributs_values);
    for ($i = 0; $i < count($attributs) - 1; $i++){
        $setCommand = $setCommand.$attributs[$i]." = \"".$attributs_values[$attributs[$i]]."\", ";
    }
    $setCommand = $setCommand.$attributs[count($attributs) - 1]." = \"".$attributs_values[$attributs[count($attributs) - 1]]."\"";
    $sql = "UPDATE $relation SET $setCommand WHERE $query";
    $stmp = $database->prepare($sql);
    $stmp->execute();
}

La fonction prend 4 paramètres, $database, $relation, $attributs_values, et $query. De la même manière que la fonction addRow() et getRows(), nous retrouvons les mêmes paramètres $database, $relation et $query, mais ici avec un autre paramètre, $attributs_values qui représente, pour simplifier au plus, un tableau de la forme array(key => value) avec l’attribut en tant que key et la nouvelle valeur en tant que value. La fonction utilise ainsi la requête SQL UPDATE $relation SET {modifications} WHERE $query.

Supprimer un enregistrement

Voici la fonction que nous avons développé pour supprimer un enregistrement dans notre base de données :

function delRow($database, $relation, $query) {
    $sql = "DELETE FROM $relation WHERE $query";
    $stmp = $database->prepare($sql);
    $stmp->execute();
}

La fonction prend 3 paramètres, $database, $relation, et $query. De la même manière que la fonction addRow() et getRows(), et updateRows() nous retrouvons les mêmes paramètres $database, $relation et $query. La fonction utilise ainsi la requête SQL DELETE FROM $relation WHERE $query.

D’autres fonctions agissant sur la base de données

On remarque finalement une structure souvent assez similaire, plus ou moins complexe. Au niveau des paramètres pour la fonction, on retrouve toujours $database, une variable qui indique donc sur quelle base de données on agit. Les autres paramètres demandés sont en rapport avec la requête SQL que l’on souhaite exécuter. Que ce soit la création, la lecture, la modification ou la suppression, on a toujours besoin d’indiquer la $relation qui est donc une variable indiquant dans quelle relation (table) on va faire cette requête. Suite à cela, les autres variables dépendent de la requête.

D’autres fonctions peuvent être pratiques, comme une fonction pour récupérer le nombre d’enregistrements pour une table ($relation) selon une condition de recherche ($query) :

function rowsCount($database, $relation, $query) {
    $sql = "SELECT * FROM $relation WHERE $query";
    $stmp = $database->prepare($sql);
    $stmp->execute();
    return $stmp->rowCount();
}

Par exemple, je veux le nombre de sessions actuellement actives :

$db = new PDO("mysql:host=localhost:3306;dbname=web_snt_nsi_xyz", "root", "root");
$nbr_sessions_actives = rowsCount($db, "sessions", "status = 1");

Il y a bien plus de fonctions que nous avons codées en avance pour pouvoir les réutiliser plus tard, comme createSession() qui va crée un session dans la base de donnée, getRowsInJson(), qui fait la même chose que getRows() mais qui renvoie le résultat sous un autre format (le format JSON). De même on retrouve une fonction qui crée un utilisateur, qui le modifie, qui le supprime, en bref on s’est fait notre boite à outils de fonctions.

/**
* Cette fonction crée un enregistrement dans la table sessions avec des informations sur la sessions :
* son code, le propriétaire de la session, la date de création, son statut, etc. 
*/

function createSession($database, $id_owner) {
    $codeSession = generateSessionCode($database);
    addRow($database, "sessions", array($codeSession, $id_owner, date('Y-m-d H:i:s', time()), 1));
    $id_session_created = getRows($database, "sessions", "*", "id_owner = $id_owner AND status = 1")["id"];
    updateLocalDB("[]", "../js/db-$id_session_created.json");
}

/**
* Il est intéressant de comparer cette fonction avec getRows() pour voir les différences qu'il y a entre les deux.
*/

function getRowsInJSON($database, $relation, $attribut, $query) {
    $sql = "SELECT $attribut FROM $relation WHERE $query";
    $stmp = $database->prepare($sql);
    $stmp->execute();
    return json_encode($stmp->fetchAll());
}

function getRows($database, $relation, $attribut, $query, $force_multiples_rows = 0) {
    $sql = "SELECT $attribut FROM $relation WHERE $query";
    $stmp = $database->prepare($sql);
    $stmp->execute();
    return rowsCount($database, $relation, $query) > 1 || $force_multiples_rows == 1 ? $stmp->fetchAll() : $stmp->fetch(PDO::FETCH_ASSOC);
}

Gestion des comptes

Ce système est pour les utilisateurs possédants un compte vérifié sur le site, c’est-à-dire ceux qui peuvent créer une session, ou ceux qui peuvent administrer le site.

Pour cela, comme dit plus haut, des fonctions ont été conçues pour créer, modifier et supprimer des utilisateurs. Ces fonctions manipulent directement la base de données, et passent donc par les fonctions plus basiques addRow(), updateRow(), getRows(), ou delRow(). Maintenant pour permettre ces actions, il est important de faire une interface graphique. Pour cela, une page est conçue pour pouvoir créer un utilisateur, pour avoir la liste des utilisateurs et pouvoir les gérer. Voici à quoi elle ressemble :

Suite à cela, il est important qu’il y ait un système de permissions pour pouvoir empêcher certains utilisateurs d’accéder à certaines pages. En effet, on veut que seul les administrateurs aient accès à cette page, et même à d’autres.

Le système de session

Lorsqu’un utilisateur n’a pas de session active, on lui propose de créer une session, à terme, il pourra configurer certains éléments de la session (notamment la durée).

Le système de session inclut un système de génération de codes, qui permettra à d’autres utilisateurs de rejoindre la session liée à ce code.

Une session va permettre à son propriétaire d’administrer les utilisateurs temporaires (les élèves) et d’observer leur avancement sur les énigmes.

De plus, pour les administrateurs, il sera possible d’avoir des statistiques combinées de toutes les sessions qui ont été créées. De même, il leur sera possible de parcourir les sessions en cours et les anciennes.

Une interface pour rejoindre une session ou se connecter

Voici l’interface de la page pour s’identifier sur le site :

On peut rejoindre une session, se connecter, créer un compte, ou accéder aux énigmes avec le menu à gauche.

Conclusion

Finalement, ce projet a été très enrichissant pour nous, il nous a vraiment permis de nous améliorer en PHP, nous avons énormément travaillé dessus, et nous sommes fiers du résultat, malgré le fait qu’on ait pas eu le temps d’intégrer tout ce qui était prévu à la base (rendez-vous à la V3 🤫).

La V2 est accessible à l’adresse : web.snt.nsi.xyz/dev/

Code source du projet

Vous pouvez retrouver le code source du projet dans notre dépôt GitHub.

Projets

PhpMonDésastre


Dans le cadre du projet SGBDR, j’ai eu l’occasion de créer un site Internet en PHP. Je vous propose donc de découvrir mon projet, nommé phpMonDésastre. Il s’agit d’une version simplifiée de phpMyAdmin qui vous permet d’interagir de manière simplifiée avec une base de données.

Idée

Mon idée était de créer une version simplifiée de phpMyAdmin où les utilisateurs pourraient venir interagir de manière minimaliste avec une base de données et observer les résultats. Cette version, que je nommerais phpMonDésastre, devrait suivre les règles du CRUD, c’est-à-dire Créer, Lire, Mettre à jour et Supprimer.

Cahier des charges

Pour accéder à mon site, les utilisateurs doivent d’abord se connecter à leur session. S’ils n’ont pas de compte, ils devront en créer un. Une fois connectés, ils pourront parcourir les différentes tables de la base de données ainsi que leur contenu. Ensuite, ils auront la possibilité d’exécuter uniquement quatre types de commandes respectant la règle CRUD : créer une table, ajouter du contenu à une table, modifier le contenu d’une table et supprimer une table. Cela permettra de limiter les possibilités des utilisateurs afin d’éviter les abus de la base de données mise à leur disposition.

Mise en place


Pour mettre en place ce projet, il faut commencer par mettre en place une base de données qui contiendra les informations des comptes utilisateurs et qui recevra ces expériences des utilisateurs.

<?php
/*On se connecte à la base de données en entrant les informations nécessaires,
telles que le nom de l'hôte, le nom de la base, les identifiants et le mot de passe.
Ensuite, on définit cette connexion sur la variable $mysqlClient que l'on utilisera
pour gérer toutes les requêtes SQL. 
On permet aussi l'affichage des erreurs pour pouvoir déboguer plus facilement*/
$sql_host="";
$sql_id="";
$sql_password="";
$sql_dbname="";

try {
    $mysqlClient = new PDO(sprintf('mysql:host=%s;dbname=%s;charset=utf8', $sql_host, $sql_dbname),
    $sql_id,
    $sql_password);

    $mysqlClient->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (Exception $exception) {
    die('Erreur : ' . $exception->getMessage());
}



Ensuite, on met en place la page de connexion. D’abord, on s’occupe de la partie frontend, puis de la partie backend.

<!DOCTYPE html>
<html lang="fr" >
<head>
    <meta charset="UTF-8">
    <title>Login Form</title>
    <link rel="stylesheet" href="style.css">
</head>
 <body>
    <?php if (!isset($_SESSION['LOGGED_USER'])) : ?> // On vérifie si l'utilisateur est connecté.
        <center><div class = "message">
            <?php if (isset($_SESSION['LOGIN_ERROR_MESSAGE'])) : ?> // Si une erreur survient, on l'affiche.
                <?php echo $_SESSION['LOGIN_ERROR_MESSAGE'];
                unset($_SESSION['LOGIN_ERROR_MESSAGE']); ?> // Une fois affichée, on l'enlève.
            <?php endif; ?>
            <?php if (isset($_SESSION['SIGNUP_ERROR_MESSAGE'])) : ?> // de même avec les erruer pour l'incription
                <?php echo $_SESSION['SIGNUP_ERROR_MESSAGE'];
                unset($_SESSION['SIGNUP_ERROR_MESSAGE']); ?>
            <?php endif; ?>
        </div></center>
        <section class = "fond-noir"> 
            <div class="signin"> 
                <div class="content"> 
                    <h2>Sign In</h2> 
                        <form class = 'form' action="submit_login.php" method="POST"> 
                            <div class="inputBox" action="submit_login.php" method="POST"> // Boîte pour demander l'adresse e-mail.
                                <input type="Email" name="email" required> <i>Username</i> 
                            </div> 
                            <div class="inputBox" action="submit_login.php" method="POST"> // Boîte pour demander le mot de passe.
                                <input type="password" name="password" required> <i>Password</i> 
                            </div> 
                            <div class="links"> 
                                <a href="forgot_psw.php">Forgot Password</a> <a href="sing_up.php">Signup</a> // Bouton renvoyant vers la page de réinitialisation de mot de passe et la page d'inscription.
                            </div> 
                            <div class="inputBox"> 
                                <input type="submit" value="Login"> // Envoyer la requête de connexion.
                            </div>
                        </form>
                    </div> 
                </div> 
            </div> 
        </section>
    <?php endif; ?>
 </body>
</body>
</html>
<?php
// On démarre la session.
session_start();
/* On importe les différentes pages PHP nécessaires pour l'utilisation complète du script,
telles que "databaseconnect.php" mentionnée ci-dessus pour se connecter à la base de données.*/
require_once(__DIR__ . '/config/mysql.php');
require_once(__DIR__ . '/databaseconnect.php');
require_once(__DIR__ . '/variables.php');
require_once(__DIR__ . '/functions.php');

$postData = $_POST;

// On vérifie que les informations demandées soient bien rentrées.
if (isset($postData['email']) && isset($postData['password'])) { 
  	// On vérifie la validité de l'email fourni.
    if (!filter_var($postData['email'], FILTER_VALIDATE_EMAIL)) {
        $_SESSION['LOGIN_ERROR_MESSAGE'] = 'Il faut un email valide pour soumettre le formulaire.';
    } else {
      	// On vérifie la présence des informations données dans la base de données.
        foreach ($users as $user) {
            if (
                $user['email'] === $postData['email'] &&
                $user['password'] === $postData['password']
            ) {
                $_SESSION['LOGGED_USER'] = [
                    'email' => $user['email'],
                    'user_id' => $user['user_id'],
                ];
            }
        }
		// Si elles ne sont pas présentes dans la base de données, on renvoie une erreur.
        if (!isset($_SESSION['LOGGED_USER'])) {
            $_SESSION['LOGIN_ERROR_MESSAGE'] = sprintf(
                'Les informations envoyées ne permettent pas de vous identifier : (%s/%s)',
                $postData['email'],
                strip_tags($postData['password'])
            );
        }
      // De même pour la page d'inscription.
        if (isset($_SESSION['SIGNUP_ERROR_MESSAGE'])) {
            $_SESSION['SIGNUP_ERROR_MESSAGE'] = sprintf(
                'Les informations envoyées ne permettent pas de vous inscrire'
            );
        }
    }

    redirectToUrl('index.php');
}


En ce qui concerne la page d’inscription, c’est à peu près le même principe, sauf qu’au lieu de vérifier la présence des informations entrées dans la base de données, on les inscrit dans celle-ci avant de rediriger l’utilisateur vers la page de connexion afin qu’il puisse se connecter avec son profil fraîchement créé.


Une fois cette étape franchie, vous arrivez sur une page de connexion semblable à celle-ci :


Une fois connecté, on arrive sur la page principale.

Pour concevoir la page principale, j’ai élaboré une maquette que je suivrai pour la création de la page :

Tout d’abord, nous procédons à l’affichage des tables :

<form class="form" action="index.php" method="post"> // Affichage des tables
    <?php foreach ($TABLES as $table) : ?>
         <?php if ($table[0] === 'users') : ?>
         <?php else : ?>
             <input type="checkbox" value="<?= $table[0] ?>" name="choix_cle" onchange="disableOtherCheckboxesTable(this)" <?= isset($_SESSION['choix_cle']) && $_SESSION['choix_cle'] === $table[0] ? 'checked' : '' ?>>
             <label for="<?= $table[0] ?>"> <?= $table[0] ?></label><br>
         <?php endif ?>
    <?php endforeach; ?>
    <div calss = "case-boutton">
         <input class="submit-bouton" type="submit" value="Valider">
    </div>
</form>

L’affichage des colonnes :

<form class="form" action="index.php" method="post">
     <?php foreach ($CLE as $clecle) : ?>
          <input type="checkbox" name="choix_contenu" value="<?= $clecle['Field'] ?>" <?= isset($_POST['choix_contenu']) && $_POST['choix_contenu'] === $clecle['Field'] ? 'checked' : '' ?>>
          <label for="<?= $clecle['Field'] ?>"> <?= $clecle['Field'] ?></label><br>
     <?php endforeach; ?>
     <div calss = "case-boutton">
          <input class="submit-bouton" type="submit" value="Valider">
     </div>
</form>

L’affichage du contenu :

<form class="form" action="index.php" method="post">
     <?php foreach ($CONT as $cont1) : ?>
          <?php foreach($cont1 as $cont2) : ?>
               <input type="checkbox" name="choix_contenu" value="<?= $cont2 ?>" <?= isset($_POST['choix_contenu']) && $_POST['choix_contenu'] === $cont2 ? 'checked' : '' ?>>
               <label for="<?= $cont2 ?>"> <?= $cont2 ?></label><br>
          <?php endforeach; ?>
     <?php endforeach; ?>
</form>

Ensuite, on procède à l’affichage des commandes :

<div class="div-submit-bouton">
     <form class="form" action="index.php" method="post">
          <center>
          CREATE TABLE IF NOT EXISTS <input class="inputBox2" type="text" name="cree_table" required> (<br><input class="inputBox1" type="text" name="cree_cont" required>);
          <input class="submit-bouton2" type="submit" value="Crée une table"/>
          </center>
     </form>
     <form class="form" action="index.php" method="post">
          <center>
          INSERT INTO <input class="inputBox2" type="text" name="ajout_table" required> (<input class="inputBox2" type="text" name="ajout_valeurs1" required>) VALUES (<input class="inputBox2" type="text" name="ajout_valeurs2" required>);
          <input class="submit-bouton2" type="submit" value="Ajouté"/>
          </center>
     </form>
     <form class="form" action="index.php" method="post">
          <center>
          UPDATE <input class="inputBox2" type="text" name="modif_table" required> SET <input class="inputBox2" type="text" name="modif_def" required> WHERE <input class="inputBox2" type="text" name="modif_condi" required>;
          <input class="submit-bouton2" type="submit" value="Modifier"/>
          </center>
     </form>
     <form class="form" action="index.php" method="post">
          <center>
          DROP TABLE <input class="inputBox2" type="text" name="drop_table" required>;
          <input class="submit-bouton2" type="submit" value="Supprimé"/>
          </center>
     </form>
</div>

Et voici le résultat :

Conclusion

En conclusion, phpMonDésastre représente une tentative de créer une version simplifiée de phpMyAdmin, offrant une interaction minimale avec une base de données. Bien que le projet suive les principes du CRUD et offre des fonctionnalités de base telles que la création, la lecture, la mise à jour et la suppression de données, il reste encore des améliorations à apporter. L’intégration de fonctionnalités supplémentaires et l’amélioration de l’interface utilisateur pourraient rendre cette plateforme plus conviviale et fonctionnelle. En fin de compte, phpMonDésastre constitue un premier pas vers un outil de gestion de base de données simplifié, mais il reste encore du travail à faire pour atteindre son plein potentiel.

Dans le lien de téléchargement ci-dessous, vous trouverez le projet dans son intégralité. Si vous utilisez un environnement local, vous devrez utiliser XAMPP pour exécuter PHP. Si vous hébergez le projet sur un serveur, vous devrez modifier la configuration de « databaseconnect.php » comme présenté précédemment.

Projets

SPA – les bases de données

En 2022, on estime que 41 186 animaux ont été adoptés à la SPA et 44 199 animaux ont été recueillis par celle-ci. Afin de pouvoir répertorier les animaux présents à la SPA, une table de données est l’outil indispensable.

I – Présentation du projet

Pour commencer, la réalisation de la base de données se porte sur la simulation d’une SPA. Celle-ci se compose d’une page d’Acceuil comportant le menu en haut de la page.

Puis d’une page Ajouter où l’on rentre les informations sur un nouvel animal.

Par la suite, d’une page Modifier permettant de modifier les informations sur un animal déjà enregistré sur la base de données en rentrant son id et en choisissant la donnée à modifier. De plus, lorsque vous choisissez de supprimer un animal, il vous suffit de rentrer son id et de cliquer sur Supprimer l’animal ayant cet id.

Et enfin, la page Parcourir où l’on choisit par quel type de donnée nous souhaitons faire la recherche (id, espèce, race, nom, date et lieu) et où l’on écrit la valeur recherchée ou si c’est une date, si elle est égale, plus grande, plus petites, etc.

Pour chaque page, il est possible de supprimer toute la table, c’est-à-dire supprimer tout le tableau crée. Lorsque vous ajoutez et modifiez, le résultat de Parcourir se trouve sur la fenêtre blanche en dessous de Rechercher.

II – Mise en place du projet

Dans un premier temps, il a été primordial d’importer les librairies présentes pour la création de la base de données.

from tkinter import *
from tkinter import ttk
from tkinter.messagebox import showinfo
import sqlite3

La première ligne de code permet d’utiliser le nom des classes et fonctions définies sur tkinter. La seconde permet de créer des widgets. La troisième, showinfo permet de fournir des instructions à l’utilisateur. Enfin, d’importer le module SQLite3 permet d’interagir avec la base de données en .db .

Après avoir importé les modules dont on a besoin, il faut établir une connexion avec la base de données « base_SPA.db » et créer un curseur pour exécuter les commandes SQL .

connection = sqlite3.connect('base_SPA.db')
curseur = connection.cursor()
Ensuite, il faut créer les différentes fenêtres telles que accueil, ajouter, modifier, parcourir qui sont présents tout le long et dès le début à la page Accueil avec le bouton pour vider la table, c'est-à-dire effacer les données de la table.
from tkinter import *
from tkinter import ttk
from tkinter.messagebox import showinfo
import sqlite3

#génération du lien de la base de donnée
connection = sqlite3.connect('base_SPA.db')
curseur = connection.cursor()

#génération de la base de donnée si elle n'existe pas
curseur.execute("CREATE TABLE IF NOT EXISTS ANNIMAL(id INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE, nom TEXT, espèce TEXT, race TEXT, age INT, trouvé_où TEXT)")

connection.commit()
curseur.close()
connection.close()

# création de toutes les fenêtres
def afficher_nouvelle_fenetre(message):
    fenetre.withdraw()  # Masque la fenêtre actuelle
    nouvelle_fenetre = Toplevel(fenetre)
    nouvelle_fenetre.title("Nouvelle Fenêtre")
    nouvelle_fenetre.geometry("720x555") 
    Label(nouvelle_fenetre, text=message).pack(padx=20, pady=20)

def affiche_acceuil():
    cadre_pile.append(cadre_acceuil)  # création de la page d'accueil
    afficher_cadre(cadre_acceuil)

def affiche_ajoute():
    cadre_pile.append(cadre_ajoute)  # création de la page ajoute
    afficher_cadre(cadre_ajoute)

def affiche_modifier():
    cadre_pile.append(cadre_modifier)  # création de la page modifier
    afficher_cadre(cadre_modifier)
    
def affiche_parcourir():
    cadre_pile.append(cadre_parcourir)  # création de la page parcourir
    afficher_cadre(cadre_parcourir)


def afficher_cadre(cadre):
    for c in [cadre_acceuil, cadre_ajoute, cadre_modifier, cadre_parcourir]:
        c.pack_forget()
    cadre.pack()

#génération de la fenêtre
fenetre = Tk()

fenetre.title("SPA - les bases de données")
fenetre.geometry("720x555")
fenetre.config(bg="#C2C2C2")
fenetre.iconbitmap("logo.ico")

#fonction pour vider la table
def vider_table():
    connection = sqlite3.connect('base_SPA.db')
    curseur = connection.cursor()
    curseur.execute("DROP TABLE ANNIMAL")
    curseur.execute("CREATE TABLE ANNIMAL(id INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE, nom TEXT, espèce TEXT, race TEXT, age INT, trouvé_où TEXT)")
    connection.commit()
    curseur.close()
    connection.close()
    
bouton_vider_table = Button(fenetre, text="vider la table", font=("Consolas",15), bg="white", fg="black",command = vider_table)
bouton_vider_table.place(x=530,y=480)

# Cadres pour différentes pages
cadre_acceuil = Frame(fenetre, bg="#C2C2C2")
cadre_ajoute = Frame(fenetre, bg="#C2C2C2")
cadre_modifier = Frame(fenetre, bg="#C2C2C2")
cadre_parcourir = Frame(fenetre, bg="#C2C2C2")

cadre_acceuil.pack()

# Création du menu
menubar = Menu(fenetre)

# Ajout des commandes directement au menu principal
menubar.add_command(label="Acceuil", command=affiche_acceuil)
menubar.add_command(label="Ajouter", command=affiche_ajoute)
menubar.add_command(label="Modifier", command=affiche_modifier)
menubar.add_command(label="Parcourir", command=affiche_parcourir)

# Configuration de la fenêtre avec la barre de menus
fenetre.config(menu=menubar)

# Initialisation de la pile des cadres
cadre_pile = [cadre_acceuil]

fenetre.mainloop()

Les fonctions permettent de gérer la navigation entre les différentes pages en fonction des actions de l’utilisateur. De plus, elles permettent de masquer les cadres précédemment affichés et affichent le cadre correspondant à la page demandée.

Après avoir créé nos différentes pages, il faut les construire, rajouter du texte, du champ de texte, etc. Tout d’abord, commence la page d’accueil, très simplement, on crée des blocs :

# création des "blocs" 
titre_acceuil = Label(cadre_acceuil, text="Bienvenue sur la base de données de la SPA", font=("Consolas", 20), bg="#C2C2C2", fg="black")
soustitre_acceuil = Label(cadre_acceuil, text="DONNONS-LEUR AUTANT QU'ILS NOUS APPORTENT !", font=("Consolas", 13), bg="#C2C2C2", fg="black")

# affichage de ces blocs
titre_acceuil.pack(pady=20)
soustitre_acceuil.pack()

Après cela, il ne nous reste plus qu’à créer les autres pages.

# création des different blocs + affichage des blocs
titre_ajoute = Label(cadre_ajoute, text="AJOUTER", font=("Consolas", 20), bg="#C2C2C2", fg="black")
titre_ajoute.pack(pady=20)
soustitre_ajoute = Label(cadre_ajoute, text="Veuillez entrer les données de l'animal", font=("Consolas", 13), bg="#C2C2C2", fg="black")
soustitre_ajoute.pack()
desc_chmp_nom_str_ajoute = Label(cadre_ajoute, text="entrez son nom", font=("Consolas", 13), bg="#C2C2C2", fg="black")
desc_chmp_nom_str_ajoute.pack()
chmp_nom_str_ajoute = Entry(cadre_ajoute, font=("Helvetica",15), bg="#ffffff", fg="black", width=30)
chmp_nom_str_ajoute.pack()
desc_chmp_espece_str_ajoute = Label(cadre_ajoute, text="entrez son espèce", font=("Consolas", 13), bg="#C2C2C2", fg="black")
desc_chmp_espece_str_ajoute.pack()
chmp_espece_str_ajoute = Entry(cadre_ajoute, font=("Helvetica",15), bg="#ffffff", fg="black", width=30)
chmp_espece_str_ajoute.pack()
desc_chmp_race_str_ajoute = Label(cadre_ajoute, text="entrez sa race", font=("Consolas", 13), bg="#C2C2C2", fg="black")
desc_chmp_race_str_ajoute.pack()
chmp_race_str_ajoute = Entry(cadre_ajoute, font=("Helvetica",15), bg="#ffffff", fg="black", width=30)
chmp_race_str_ajoute.pack()
desc_chmp_age_str_ajoute = Label(cadre_ajoute, text="entrez son âge", font=("Consolas", 13), bg="#C2C2C2", fg="black")
desc_chmp_age_str_ajoute.pack()
chmp_age_str_ajoute = Entry(cadre_ajoute, font=("Helvetica",15), bg="#ffffff", fg="black", width=30)
chmp_age_str_ajoute.pack()
desc_chmp_localisation_str_ajoute = Label(cadre_ajoute, text="entrez le lieu où il a été trouvé", font=("Consolas", 13), bg="#C2C2C2", fg="black")
desc_chmp_localisation_str_ajoute.pack()
chmp_localisation_str_ajoute = Entry(cadre_ajoute, font=("Helvetica",15), bg="#ffffff", fg="black", width=30)
chmp_localisation_str_ajoute.pack()

#fonction pour ajouter les donnée choisies dans les champs de texte de la page ajouter dans la base de donnéee
def ajouter_donnee():
    connection = sqlite3.connect('base_SPA.db')
    curseur = connection.cursor()
    data = (chmp_nom_str_ajoute.get(),chmp_espece_str_ajoute.get(),chmp_race_str_ajoute.get(),chmp_age_str_ajoute.get(),chmp_localisation_str_ajoute.get())
    curseur.execute("INSERT INTO ANNIMAL (nom,espèce,race,age,trouvé_où) VALUES (?,?,?,?,?)",data)
    connection.commit()
    curseur.close()
    connection.close()

# création et placement du bouton utiliser pour valider l'ajout de donnée
bouton_ajouter = Button(cadre_ajoute, text="Ajouter l'annimal", font=("Consolas",15), bg="white", fg="black",command = ajouter_donnee)
bouton_ajouter.pack(pady=20)

Dans ce code, nous créons la fenêtre Ajouter avec les informations à remplir. Lorsque l’on clique sur le bouton Ajouter l’animal, celui ci sera ajouté automatiquement à la table de données.

# création des different blocs + affichage des blocs
titre_modifier = Label(cadre_modifier, text="MODIFIER", font=("Consolas", 20), bg="#C2C2C2", fg="black")
titre_modifier.pack(pady=20)
desc_chmp_modifier = Label(cadre_modifier, text="entrez l'id de l'annimal", font=("Consolas", 13), bg="#C2C2C2", fg="black")
desc_chmp_modifier.pack()
chmp_str_modifier = Entry(cadre_modifier, font=("Helvetica",15), bg="#ffffff", fg="black", width=5)
chmp_str_modifier.pack()

# création de la liste déroulante
desc_liste_deroul_modifier = Label(cadre_modifier, text="Choissisez quel donnée voulez vous modifier", font=("Consolas", 13), bg="#C2C2C2", fg="black")
desc_liste_deroul_modifier.pack(pady=10)
liste_choix = ["Nom","Race","Espèce","Age","Localisation"]
liste_deroulante = ttk.Combobox(cadre_modifier, values=liste_choix)
liste_deroulante.pack()

desc_chmp2_modifier = Label(cadre_modifier, text="entrez la modification", font=("Consolas", 13), bg="#C2C2C2", fg="black")
desc_chmp2_modifier.pack(pady=10)
chmp2_str_modifier = Entry(cadre_modifier, font=("Helvetica",15), bg="#ffffff", fg="black", width=30)
chmp2_str_modifier.pack()

# fonction pour modifier une donnée dans la base de donnée à partir de la liste déroulante et le champ de texte
def modifier_donnee():
    select = liste_deroulante.get()
    datas = (chmp2_str_modifier.get(),chmp_str_modifier.get())
    connection = sqlite3.connect('base_SPA.db')
    curseur = connection.cursor()
    if select == "Nom":
        curseur.execute("UPDATE ANNIMAL SET nom = ? WHERE id = ?",datas)
    if select == "Race":
        curseur.execute("UPDATE ANNIMAL SET race = ? WHERE id = ?",datas)
    if select == "Espèce":
        curseur.execute("UPDATE ANNIMAL SET espèce = ? WHERE id = ?",datas)
    if select == "Age":
        curseur.execute("UPDATE ANNIMAL SET age = ? WHERE id = ?",datas)
    if select == "Localisation":
        curseur.execute("UPDATE ANNIMAL SET trouvé_où = ? WHERE id = ?",datas)
    connection.commit()
    curseur.close()
    connection.close()

# fonction pour supprimer toutes les données lié à un identifiant dans la base de donnée   
def supprimer_donnee():
    sup_select = chmp_str_modifier.get()
    connection = sqlite3.connect('base_SPA.db')
    curseur = connection.cursor()
    curseur.execute("DELETE FROM ANNIMAL WHERE id = ?",sup_select)
    connection.commit()
    curseur.close()
    connection.close()

# création et placement des boutons pour valider les modifications / suppressions
bouton_sup = Button(cadre_modifier, text="Supprimer l'annimal ayant cet id", font=("Consolas",15), bg="white", fg="black", command = supprimer_donnee)
bouton_sup.pack(pady=30)
bouton_modifier = Button(cadre_modifier, text="Modifier", font=("Consolas",15), bg="white", fg="black", command = modifier_donnee)
bouton_modifier.pack()

La fenêtre modifier quant à elle a besoin de l‘id de l’animal. En effet, pour modifier une information sur un animal, la personne doit rentrer l’id présente sur la table de donnée correspondant à l’animal, après cela, elle choisit l‘information à modifier dans la liste déroulante puis rentre la correction à faire. Ensuite, cette fenêtre abrite aussi la possibilité de supprimer l’animal comme cela a été dit précédemment grâce à la fonction supprimer_donnee() par l’id de l’animal.

# création des different blocs + affichage des blocs
titre_parcourir = Label(cadre_parcourir, text="PARCOURIR", font=("Consolas", 20), bg="#C2C2C2", fg="black")
titre_parcourir.pack(pady=10)
desc_liste_deroulante_parcourir = Label(cadre_parcourir, text="Choississez par qu'elle type de donnee souhaitez vous rechercher", font=("Consolas", 13), bg="#C2C2C2", fg="black")
desc_liste_deroulante_parcourir.pack(pady=10)

# création de la liste déroulante du type de donnée
liste_deroulante_parcourir = ttk.Combobox(cadre_parcourir, values=["id","Nom","Espèce", "Age","Race","Localisation"])
liste_deroulante_parcourir.set("id")
liste_deroulante_parcourir.pack(pady=10)

# création du cadre blanc où apparaissent les recherches effectuées
frame_info_recherche = Frame(cadre_parcourir, bg="#C2C2C2",heigh = 50,width=650)
frame_info_recherche.pack_propagate(False) 
frame_info_recherche.pack()

desc_chmp_str_parcourir = Label(frame_info_recherche, text="écrivez cette information de recherche", font=("Consolas", 10), bg="#C2C2C2", fg="black")
desc_chmp_str_parcourir.place(x=0,y=0)

chmp_str_parcourir = Entry(frame_info_recherche, font=("Helvetica",10), bg="#ffffff", fg="black", width=15)
chmp_str_parcourir.place(x=0,y=23)

desc_chmp_str_parcourir = Label(frame_info_recherche, text="Si c'est une valeur elle doit être :", font=("Consolas", 10), bg="#C2C2C2", fg="black")
desc_chmp_str_parcourir.place(x=390,y=0)

# création de la liste déroulante pour préciser la recherche par valeurs numériques
liste_deroulante_info_recherche = ttk.Combobox(frame_info_recherche, values=["=","<=",">=","<",">"],width=15)
liste_deroulante_info_recherche.set("=")
liste_deroulante_info_recherche.place(x=390,y=23)

variable = StringVar()
variable.set("1")
chmp_str_info_recherche_parcourir = Entry(frame_info_recherche,textvariable=variable, font=("Helvetica",10), bg="#ffffff", fg="black", width=15)
chmp_str_info_recherche_parcourir.place(x=510,y=23)

# création de la variable qui stocke tout le résultat d'une recherche
global recherche
recherche = ""

# fonction pour construire notre variable
def parcourir_donnee():
    global recherche
    recherche = ""
    connection = sqlite3.connect('base_SPA.db')
    curseur = connection.cursor()
    data = [chmp_str_parcourir.get()]
    data2 = [chmp_str_info_recherche_parcourir.get()]
    if liste_deroulante_parcourir.get() == "Nom":
        recherche = curseur.execute("SELECT nom,espèce,race,age,trouvé_où FROM ANNIMAL WHERE nom = ?",data).fetchall()
    if liste_deroulante_parcourir.get() == "Race":
        recherche = curseur.execute("SELECT nom,espèce,race,age,trouvé_où FROM ANNIMAL WHERE race = ?",data).fetchall()
    if liste_deroulante_parcourir.get() == "Espèce":
        recherche = curseur.execute("SELECT nom,espèce,race,age,trouvé_où FROM ANNIMAL WHERE espèce = ?",data).fetchall()
    if liste_deroulante_parcourir.get() == "Localisation":
        recherche = curseur.execute("SELECT nom,espèce,race,age,trouvé_où FROM ANNIMAL WHERE trouvé_où = ?",data).fetchall()
    if liste_deroulante_parcourir.get() == "Age":
        if liste_deroulante_info_recherche.get() == "=":
            recherche = curseur.execute("SELECT nom,espèce,race,age,trouvé_où FROM ANNIMAL WHERE age = ?",data2).fetchall()
        elif liste_deroulante_info_recherche.get() == "<":
            recherche = curseur.execute("SELECT nom,espèce,race,age,trouvé_où FROM ANNIMAL WHERE age < ?",data2).fetchall()
        elif liste_deroulante_info_recherche.get() == ">":
            recherche = curseur.execute("SELECT nom,espèce,race,age,trouvé_où FROM ANNIMAL WHERE age > ?",data2).fetchall()
        elif liste_deroulante_info_recherche.get() == "<=":
            recherche = curseur.execute("SELECT nom,espèce,race,age,trouvé_où FROM ANNIMAL WHERE age <= ?",data2).fetchall()
        elif liste_deroulante_info_recherche.get() == ">=":
            recherche = curseur.execute("SELECT nom,espèce,race,age,trouvé_où FROM ANNIMAL WHERE age >= ?",data2).fetchall()
    if liste_deroulante_parcourir.get() == "id":
        if liste_deroulante_info_recherche.get() == "=":
            recherche = curseur.execute("SELECT nom,espèce,race,age,trouvé_où FROM ANNIMAL WHERE id = ?",data2).fetchall()
        elif liste_deroulante_info_recherche.get() == "<":
            recherche = curseur.execute("SELECT nom,espèce,race,age,trouvé_où FROM ANNIMAL WHERE id < ?",data2).fetchall()
        elif liste_deroulante_info_recherche.get() == ">":
            recherche = curseur.execute("SELECT nom,espèce,race,age,trouvé_où FROM ANNIMAL WHERE id > ?",data2).fetchall()
        elif liste_deroulante_info_recherche.get() == "<=":
            recherche = curseur.execute("SELECT nom,espèce,race,age,trouvé_où FROM ANNIMAL WHERE id <= ?",data2).fetchall()
        elif liste_deroulante_info_recherche.get() == ">=":
            recherche = curseur.execute("SELECT nom,espèce,race,age,trouvé_où FROM ANNIMAL WHERE id >= ?",data2).fetchall()
    connection.commit()
    curseur.close()
    connection.close()

# fonction pour afficher la recherche sous une forme lisible
def texte_parcourir():
    texte_widget_frame_parcourir.delete("1.0","end")
    parcourir_donnee()
    for car in str(recherche):
        if car == "[" or car == "]" or car == "(" or car == "'"  :
            texte_widget_frame_parcourir.insert(INSERT,"")
        elif car == ",":
            texte_widget_frame_parcourir.insert(INSERT,"")
        elif car == ")":
            texte_widget_frame_parcourir.insert(INSERT,"\n")
        else :
            texte_widget_frame_parcourir.insert(INSERT,car)
  
# création et placement du bouton pour valider une recherche
bouton_parcourir = Button(cadre_parcourir, text="Rechercher", font=("Consolas",15), bg="white", fg="black",command = texte_parcourir)
bouton_parcourir.pack(pady=10)


frame_parcourir = Frame(cadre_parcourir, bg="#ffffff",heigh = 210,width=650)
frame_parcourir.pack_propagate(False) 
frame_parcourir.pack()

# création et placement de la barre de scroll lié à la recherche effectuée
scrollbar = Scrollbar(frame_parcourir, orient=VERTICAL)
scrollbar.pack(side=RIGHT, fill=Y)

texte_widget_frame_parcourir = Text(frame_parcourir, yscrollcommand=scrollbar.set)
texte_widget_frame_parcourir.pack(fill=BOTH, expand=True)

scrollbar = Scrollbar(texte_widget_frame_parcourir)

scrollbar.config(command=texte_widget_frame_parcourir.yview)

texte_recherche_parcourir = Text(frame_parcourir)

Dans ce code, nous créons la fenêtre Parcourir avec toutes ses informations.

Puis, finalement, on peut terminer le script :

# Affichage de la page d'accueil au lancement du script
cadre_acceuil.pack()

# Création du menu
menubar = Menu(fenetre)

# Ajout des commandes directement au menu principal
menubar.add_command(label="Acceuil", command=affiche_acceuil)
menubar.add_command(label="Ajouter", command=affiche_ajoute)
menubar.add_command(label="Modifier", command=affiche_modifier)
menubar.add_command(label="Parcourir", command=affiche_parcourir)

# Configuration de la fenêtre avec la barre de menus
fenetre.config(menu=menubar)

# Initialisation de la pile des cadres
cadre_pile = [cadre_acceuil]

fenetre.mainloop()

Cette fin de script permet d’afficher l’interface et ses informations. Sans cela, Le script ne serait pas utilisable

III – Conclusion

Ainsi, ce projet nous a permis de mobiliser nos bases acquises en python et SQL pour monter un projet traitant sur les bases de données. Ce type de projet n’est pas des plus simples compte tenu de la technique à avoir et des difficultés rencontrées telles que le positionnement des blocs et les interactions avec la base de données. Pour continuer, il est important dans ce type de projets de comprendre son code ligne à ligne afin de ne pas se tromper sur un nom, une valeur ou autres.

Projets

La version améliorée de PixChoice

PixChoice est un site créé par Vincent ROBERT, Kevin FEDYNA et Ilyas R. Le site affiche 6 images de manières aléatoires parmi une liste afin de pouvoir voter pour la meilleure proposition. Le site calcule par la suite les résultats et permet de les afficher. Et j’en ai fait une version améliorée pour faciliter la modification des paramètres ou des utilisateurs notamment et je vais la présenter.

L’idée

Comme dit dans l’extrait le site propose 6 images parmi une liste et vous permet de voter pour celle qui vous plaît le plus. Cependant lors de sa V1 le site ne permettait pas un changement de paramètre optimal ce qui obligeait de se souvenir de l’utilisation de l’année passé et ce qui était une perte de temps, j’ai donc créé la V2 qui ajoute un interface administrateur qui permet de paramétrer les images, le vote et l’affichage des résultats. Pour cela je me suis appuyé sur le code de la V1 trouvable ici ainsi que le framework CSS PureCSS.io (Un framework CSS est un ensemble prédéfini de styles et de composants prêts à l’emploi pour faciliter le développement web). C’est ainsi que j’ai pu concevoir la V2 de PixChoice

Quelques images du site

Vous avez donc ici quelques images du site de la vue d’un utilisateur ne possédant pas de compte et qui ne peut accéder qu’à la page de vote, aux résultats et à la page de connexion.

Fonctionnement

Je vais vous faire une brève explication du fonctionnement du site :

Les différentes pages du site sont faites en php c’est à dire que dans la page écrite en html on peut y ajouter du code, des fonctions… C’est ainsi que les pages du site sont dynamiques et s’affichent selon les paramètres. Et en parlant de paramètres, il faut bien les stocker mais où ? Pour stocker les informations, une base de donnée à été mise à ma disposition afin de sauvegarder des informations comme les utilisateurs avec leur identifiant et leur mot de passe, les images avec leur nombre de vote et combien de fois elles sont apparues, l’adresse ip des votants et la configuration du site. Et comme le php permet nativement de faire des requêtes sql l’utilisation de la base de donnée est simplifiée.

Dans les fonctionnalitées de php on peut également inclure d’autres page, c’est à dire que le code de la page donnée en paramètre va être placé au niveau de l’appel via la fonction ce qui me permet de n’avoir que 3 pages principales, c’est à dire que seulement 3 pages vont être les fondations du sites et les autres pages vont seulement remplir les 3 pages principales. On le retrouve notamment dans le code de la page index.php

<?php
session_start(); // Création d'une session pour sauvegardé des données
// Appel de différentes pages pour instancier ...
require_once(__DIR__ . '/config.php'); // Les identifiants de la base de donnée
require_once(__DIR__ . '/databaseconnect.php'); // Une connexion à la base de donnée
require_once(__DIR__ . '/variables.php'); // Des variables récupérées en sql
require_once(__DIR__ . '/fonctions.php'); // Des fonctions

cookie($temps_vote[0][0]); // Appel de la fonction cookie qui va créer un cookie 
//pour sauvegarder le nombre de vote fait
?>

<!doctype html> <!-- Code html basique -->
<html lang="fr">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Pixchoice</title>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/purecss@3.0.0/build/pure-min.css" integrity="sha384-X38yfunGUhNzHpBaEBsWLO+A0HDYOQi8ufWDkZ0k9e0eXz/tH3II7uKZ9msv++Ls" crossorigin="anonymous">
    <link rel="stylesheet" href="css/styles.css">
</head>
<body>

<?php // retour en php pour récupérer les variables envoyés en post ou en get
$postData = $_POST;
$getData = $_GET;

include('header.php'); // Ajout de l'en tête de page

// ajout de la page de connexion si des variables existent
if (isset($postData['connexion']) || isset($_GET['connexion'])) { 
    include('login.php');
} else {
    include('vote.php'); // et si elles n'existent pas ajout de la page de vote
};?>
</body> <!-- Fin du code html -->
</html>

Vous remarquerez donc que mes pages ne sont pas bien conséquentes comme je peux créer d’autres pages pour les ajouter directement dans le code d’une autre, et c’est pourquoi j’ai 3 pages principales mais 19 pages secondaires qui sont appelées par les pages principales.

Fonctionnalités

Voici donc les différentes fonctionnalités proposées par le site afin de faciliter l’utilisation du site par les administrateurs :

  • Configuration du vote
    • Activation / Désactivation du vote.
    • Activation / Désactivation du vote pour les admins malgré la limite. (permet aux administrateurs de voter sans limites)
    • Gestion du nombre de vote avant d’être bloqué
    • Gestion du temps avant d’être débloqué
  • Configuration des images
    • Ajout d’image (avec gestion de formatage du nom et d’id dans la db)
    • Suppression d’images (avec gestion dans la db)
  • Configuration des résultats
    • Activation / Désactivation des résultats pour tous.
    • Activation / Désactivation de l’affichage des résultats pour les admins.
  • Gestion des utilisateurs
    • Ajout d’utilisateurs avec génération de mot de passe temporaire aléatoire
    • Tableau référençant les utilisateurs qui permet d’en supprimer ou d’activer ou non le fait qu’ils soient superadmin (seul les superadmin ont accès à cette page)
  • Configuration du compte
    • Modification de l’identifiant
    • Modification du mot de passe

Configuration du vote

Afin de configurer le vote plusieurs paramètres sont pris en charges:

  • Activation / Désactivation du vote.
  • Activation / Désactivation du vote pour les admins malgré la limite. (permet aux administrateurs de voter sans limites)
  • Gestion du nombre de vote avant d’être bloqué
  • Gestion du temps avant d’être débloqué

Voici donc la page permettant de configurer le vote

Configuration des images

Afin de configurer le vote plusieurs paramètres sont pris en charges:

  • Ajout d’image (avec gestion de formatage du nom et d’id dans la db)
  • Suppression d’images (avec gestion dans la db)

Voici donc la page permettant de configurer les images

Configuration des résultats

Afin de configurer le vote plusieurs paramètres sont pris en charges:

  • Activation / Désactivation des résultats pour tous.
  • Activation / Désactivation de l’affichage des résultats pour les admins.

Voici donc la page permettant de configurer les résultats

Gestion des utilisateurs

Afin de configurer le vote plusieurs paramètres sont pris en charges:

  • Ajout d’utilisateurs avec génération de mot de passe temporaire aléatoire
  • Tableau référençant les utilisateurs qui permet d’en supprimer ou d’activer ou non le fait qu’ils soient superadmin (seul les superadmin ont accès à cette page)

Voici donc la page permettant de gérer les utilisateurs

Configuration du compte

Afin de configurer le vote plusieurs paramètres sont pris en charges:

  • Modification de l’identifiant
  • Modification du mot de passe

Voici donc la page permettant de configurer son compte

Conclusion

Pour conclure le tout ce projet a été très enrichissant et très exigeant mais je me suis bien amusé à le faire, j’ai pu découvrir toute une partie du développement web étant donné que j’ai dû apprendre le php mais je m’en suis sorti et je suis plutôt fier du résultat.

Dépôt Github

Vous pouvez retrouver les fichiers sources du projet sur mon dépôt Github afin de mettre en place mon site sur votre serveur.

EDIT de l’enseignant : Le projet initial est un code de Julien Bernon, enseignant de physique-chimie et spécialité NSI. Merci à lui de nous avoir permis de reprendre son projet initial.

Projets

Application de gestion de playlist (SGBDR) avec Python et…

Dans le monde numérique d’aujourd’hui, la musique est omniprésente, et la possibilité de gérer efficacement ses playlists est essentielle pour tout amateur de musique. Dans cet article, nous allons explorer comment créer une application simple de gestion de playlist en utilisant Python et Tkinter, deux outils puissants et accessibles pour le développement d’interfaces graphiques.

Introduction à Tkinter et SQLite

Tkinter est une bibliothèque standard de Python largement utilisée pour créer des interfaces graphiques. Elle offre une variété de widgets (éléments d’interface utilisateur) tels que des boutons, des étiquettes et des cadres, permettant de construire des applications interactives.

SQLite est une bibliothèque légère de gestion de base de données relationnelle souvent intégrée dans les applications pour stocker des données de manière structurée. Dans notre cas, nous l’utiliserons pour stocker les informations sur notre playlist musicale.

Création de la structure de données

Nous allons commencer par définir la structure de notre base de données SQLite, qui stockera les détails de chaque morceau de musique dans notre playlist. Nous aurons des champs tels que le titre de la chanson, l’année de sortie, la durée, le genre, l’artiste et le type d’album.

# Création de la table dans la base de données SQLite
def create_table():
    conn = sqlite3.connect("playlist.db")
    cursor = conn.cursor()
    cursor.execute('''
        CREATE TABLE IF NOT EXISTS playlist (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            titre TEXT,
            annee INTEGER,
            duree TEXT,
            genre TEXT,
            artiste TEXT,
            album_type TEXT
        )
    ''')
    conn.commit()
    conn.close()

Dans cette fonction create_table(), nous établissons une connexion à notre base de données SQLite et exécutons une requête SQL pour créer une table nommée « playlist » avec les champs spécifiés. Cette fonction vérifie d’abord si la table existe déjà avant de la créer, grâce à la clause IF NOT EXISTS.

Lecture des données de la playlist

Une fois que la structure de la table est définie, nous pouvons écrire une fonction pour lire les données de la table et les afficher. Voici comment cela peut être fait :

# Lecture des données de la table et affichage
def read_data():
    conn = sqlite3.connect("playlist.db")
    cursor = conn.cursor()
    cursor.execute("SELECT * FROM playlist")
    rows = cursor.fetchall()
    conn.close()

    display_text = ""
    for row in rows:
        display_text += f"ID: {row[0]}, Titre: {row[1]}, Année: {row[2]}, Durée: {row[3]}, Genre: {row[4]}, Artiste: {row[5]}, Type d'album: {row[6]}\n"

    text_display.config(state=tk.NORMAL)
    text_display.delete('1.0', tk.END)
    text_display.insert(tk.END, display_text)
    text_display.config(state=tk.DISABLED)

Dans la fonction read_data(), nous établissons à nouveau une connexion à la base de données, exécutons une requête SQL pour sélectionner toutes les entrées de la table « playlist » et les récupérons à l’aide de fetchall(). Ensuite, nous bouclons à travers chaque ligne et les affichons.

Interface Graphique avec Tkinter

Maintenant que nous avons mis en place la gestion des données, nous pouvons créer une interface graphique conviviale pour notre application de gestion de playlist.

# Création de la fenêtre principale
window = tk.Tk()
window.title("Gestion de Playlist")
window.geometry("780x720")
window.minsize(680, 360)
window.configure(bg="#000")

Nous avons créé une fenêtre principale en utilisant tk.Tk() et lui avons donné un titre avec window.title(). Nous avons également défini la géométrie de la fenêtre et spécifié une couleur de fond.

Création des boutons d’action

Ensuite, nous avons ajouté des boutons pour effectuer différentes actions telles que la création de la table, la lecture des données et la manipulation des entrées de la playlist.

# Création des boutons d'action
frame_buttons = tk.Frame(window, bg="#000")
frame_buttons.pack(side=tk.TOP, pady=20)

btn_create = tk.Button(frame_buttons, text="CREATE", font=("Courrier", 14), bg="#01D758", fg="#000", command=create_table)
btn_create.pack(side=tk.LEFT, padx=10)

btn_read = tk.Button(frame_buttons, text="READ", font=("Courrier", 14), bg="#01D758", fg="#000", command=read_data)
btn_read.pack(side=tk.LEFT, padx=10)

Nous avons utilisé la classe Button de Tkinter pour créer des boutons, spécifiant leur texte, leur police, leur couleur de fond et de texte, ainsi que les actions qu’ils effectueront lorsqu’ils seront cliqués.

Conclusion

Dans cet article, nous avons exploré la création d’une application simple de gestion de playlist en utilisant Python et Tkinter. Nous avons vu comment créer une structure de base de données SQLite pour stocker les informations sur les morceaux de musique, ainsi que comment concevoir une interface graphique conviviale pour interagir avec ces données. Avec cette base, les amateurs de musique peuvent commencer à construire leur propre application de gestion de playlist personnalisée.

Projets

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_produitnom_produitprixstock
6Binnerto en acier0.001

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_clientnomprenomadresse_mailadressedate_naissancedate_inscriptionvip
8KroyableAntoninantonin.kroyable@lol.mdr13 chemin des Rippes, Salle-à-Sac10-03-200711-11-2011True

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_commandeid_client_commandeid_produitdate_commandedate_livraisonenvoyequantiteprix_total
308614-02-202219-02-2022True30.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 :

Projets

Système de gestion de base de données relationnelle sur…

Venez découvrir tout au long de ce compte rendu comment nous avons créé un programme en python permettant à l’utilisateur d’interagir avec la base de donnée que nous avons créé.

Dans un premier, on a importé toutes les bibliothèques nécessaires à la création du programme.

from tkinter import *
from tkinter import ttk
import tkinter as tk
import sqlite3
import pandas
import os

La bibliothèque « tkinter » va servir à créer l’interface graphique ( vous pouvez en apprendre plus grâce à ce tutoriel, créé par Aël, élève en Spé NSI en classe de 1ère ).

La bibliothèque sqlite3 permet d’interagir avec une base de donnée.

La bibliothèque pandas permet de récupérer des données dans un ficher csv.

Et enfin la bibliothèque os permet de supprimer des fichiers dans le cadre de ce programme.

I. Création de la page d’accueil

Nous avons d’abord réfléchi à l’interface graphique que nous souhaitions réaliser.

Il nous fallait d’abord une page d’accueil, avec quatre boutons permettant d’effectuer les quatre fonctions nécessaires à l’utilisation d’une base de données : créer la base, lire la base, modifier la base, et enfin supprimer la base.

Le code ci-dessous permet de créer la page d’accueil, contenant un titre et 4 boutons.

window = Tk() # page d'accueil avec les 4 boutons

window.title("Gérer une base de donnée sur les jeux vidéos")
window.geometry("1280x1024")
window.maxsize(1280,1024)
window.config(bg="#7EBDC2")
window.iconbitmap("manette.ico")

titre = Label(window, text="4 boutons pour intéragir avec la base de données", font=("Consolas",20), bg="#d1fdff", fg="black")
titre.pack(pady=45)

boite = Frame(window, bg="#7EBDC2")
boite.pack(pady=200)

boutonC = Button(boite, text="CREATE", font=("Consolas",15), bg="white", fg="black", command=create, width=10, height=3)
boutonC.grid(row = 1, column = 0, padx = 15, pady=15)

boutonR = Button(boite, text="READ", font=("Consolas",15), bg="white", fg="black", command=read, width=10, height=3)
boutonR.grid(row = 1, column = 1, padx = 15, pady=15)

boutonU = Button(boite, text="UPDATE", font=("Consolas",15), bg="white", fg="black", command=update, width=10, height=3)
boutonU.grid(row = 3, column = 0, padx = 15, pady=15)

boutonD = Button(boite, text="DROP\n DATABASE", font=("Consolas",15), bg="white", fg="black", command=delete, width=10, height=3)
boutonD.grid(row = 3, column = 1, padx = 15, pady=15)

window.mainloop()

Visuel graphique :

II. Comment utiliser nos fonctions

Lier une interface graphique à des fonctions nécessite l’emploi de boutons.

Il nous fallait donc créer 4 fonctions permettant d’interagir avec la base de données.

A. La fonction create()

Nos fichiers à exploiter sont au format .csv. Avec la bibliothèque pandas, nous avons stockés les fichiers csv dans des variables pour pouvoir les exploiter.

ed = pandas.read_csv("relation_editeur.csv")
jv = pandas.read_csv("base_principale.csv")

Puis nous avons dû créer un fichier au format .db (Data Base).

connection = sqlite3.connect('base.db')
curseur = connection.cursor()

Nous avons créé les deux relations en indiquant les attributs et leur type.

curseur.execute("CREATE TABLE IF NOT EXISTS ED(Id_Editeur INTEGER, Editeur TEXT, PRIMARY KEY(Id_Editeur AUTOINCREMENT))")
curseur.execute("CREATE TABLE IF NOT EXISTS JV(Id_Jeu INTEGER, Jeu TEXT, Annee INTEGER, Prix FLOAT, Plateforme TEXT, Id_Editeur INTEGER, PRIMARY KEY(Id_Jeu AUTOINCREMENT), FOREIGN KEY(Id_Editeur) REFERENCES ED(Id_Editeur))")

Et enfin, nous avons créé deux boucles « for » pour ajouter les enregistrements des fichiers .csv dans la base de données automatiquement.

for i in range(len(ed)):
    data = [ed.loc[i, "Editeur"]]
    curseur.execute("INSERT INTO ED(Editeur) VALUES (?)", data)
    
for i in range(len(jv)):
    data = []
    data2 = list(jv.loc[i, ["Jeu", "Annee", "Prix", "Plateforme", "Id_Editeur" ]])
    data2[1] = int(data2[1])
    data2[4] = int(data2[4])
    data.append(tuple(data2))
    curseur.executemany("INSERT INTO JV(Jeu, Annee, Prix, Plateforme, Id_Editeur) VALUES (?, ?, ?, ?, ?)", data)

Pour finir, nous avons mis chacun de ces éléments dans une seule fonction : create(), appelée par le premier bouton de l’accueil. Nous avons décidé d’afficher une phrase pour indiquer à l’utilisateur que la base de données a bien été créé. Un simple Label nous a suffit (allez voir le tutoriel d’Aël pour plus de détails).

B. La fonction drop_database()

En SQL, la commande « DROP DATABASE » permet de supprimer la base de données. En python, il est impossible d’utiliser cette commande. On a donc importé le module os qui permet dans ce cas de supprimer un fichier.

Ainsi, la structure de la fonction drop_database() serait :

os.remove("base.db")

Cependant, si le fichier n’existe pas, tenter de le supprimer affichera, dans l’IDE python, un message d’erreur. Pour éviter ce problème, nous avons créé une variable globale « test » qui prend la valeur 0 si la base n’existe pas, et 1 si la base existe. Nous avons donc testé avec un « if » la valeur de « test », et le fichier ne sera supprimé que si « test » est égal à 1 et un message de confirmation s’affichera, sinon un message apparaît sur la fenêtre tkinter pour indiquer que la base n’existe pas, ce qui nous donne la fonction suivante.

def drop_database(): # supprime le fichier de la base de donnée s'il existe
    global test
    global message
    global texte
    
    if test == 1:
        os.remove("base.db")
        test -= 1
        supmes()
        message = "La base de donnée a bien été suprimée"
        texte = Label(window, text=message, font=("Helvetica",15), bg="#d1fdff", fg="black")
        texte.pack()
    elif test == 0:
        supmes()
        message = "La base de donnée n'existe actuellement pas"
        texte = Label(window, text=message, font=("Helvetica",15), bg="#d1fdff", fg="black")
        texte.pack()

C. La fonction read()

Pour la santé mentale de tout lecteur, nous ne montrerons pas ici l’intégralité de notre fonction (sinon, le fichier entier est téléchargeable en bas de page).

En SQL, pour sélectionner des éléments d’une base de données, on utilise la commande « SELECT Attribut_1, Attribut_2, … FROM DB ». En python, avec la bibliothèque sqlite3, la structure est exactement la même. Or il faut récupérer ce que contient le curseur et le stocker dans une variable, pour ensuite l’afficher. Par exemple, pour afficher tous les attributs d’une relation dans la console de l’IDE Python, on utilise :

curseur.execute("SELECT * FROM RELATION")
lecture = curseur.fetchall()#récupère les données obtenues par la ligne 1 en 'str'
print(lecture)

Lorsque l’on clique sur le bouton « read » de la page d’accueil, une nouvelle fenêtre tkinter apparaît. Sur cette fenêtre, on peut choisir de lire la relation principale (relation Jeux Vidéo), la relation secondaire (relation Editeurs) ou de retourner au menu, soit à l’accueil.

Le bouton « Retour Menu » supprime simplement la fenêtre, avec la commande tkinter :

window_r.destroy()

La lecture de la relation Editeurs est assez simple. En effet, la relation Editeurs ne contient que deux attributs : la clé primaire « Id_Editeur » et le nom de l’éditeur, « Editeur ». On a donc choisi de montrer par défaut toute la relation dans la même fenêtre que celle contenant les boutons de lecture, et donc supprimer les boutons. Nous avons donc dû globaliser nos boutons, afin de les supprimer ainsi :

global bouton
bouton.pack_forget()

Enfin, il a fallu sélectionner tous les éléments et les afficher dans un Label.

curseur.execute("SELECT * FROM ED")#fonction simplifiée
affichage = curseur.fetchall()
affiche = Label(text=affichage)#plus de détails sur le tutoriel
affiche.pack()

Pour finir, il nous a fallu afficher le contenu de la relation principale selon les demandes de l’utilisateur.

Pour cela, nous avons créé, grâce à des recherches supplémentaires sur internet, des checkbutton (des cases à cocher), qui peuvent être sélectionnés ou non pour choisir si l’on affiche l’attribut.

De plus, nous avons permis à l’utilisateur de choisir de spécifier sa demande avec la commande SQL « WHERE Attribut = Valeur ». Cela nous permet donc de faire une sélection très précise d’éléments à afficher.

D. La fonction update()

La dernière fonction à créer !

Modifier la base de données implique 3 actions : ajouter un enregistrement, supprimer un enregistrement, et modifier une valeur d’un des attributs de l’enregistrement.

Nous avons décidé qu’il était impossible, grâce à l’interface, d’ajouter directement un éditeur dans la relation « Editeur ». Ainsi lors de l’ajout ou de la modification d’un enregistrement de la relation Jeux Vidéo, si l’éditeur renseigné n’existe pas dans la relation correspondante, il y est créé. Cet ajout se fait simplement :

#Selon les spécificités de la bibliothèque sqlite3, avec "Editeur" renseigné par l'utilisateur
curseur.execute("SELECT Id_Editeur FROM ED WHERE Editeur = ?",(Editeur,))
temp = curseur.fetchall()
if temp == [] : #si l'éditeur n'existe pas
  curseur.execute("INSERT INTO JV (Editeur) VALUES (?)",(Editeur,))
  curseur.execute("SELECT Id_Editeur FROM ED WHERE Editeur = ?",(Editeur,))
  id_editeur = curseur.fetchall()#après ajout dans ED, on récupère l'Identifiant pour l'ajout dans JV

Pour la fonction ajouter(), nous avons créé 5 entrées correspondant aux valeurs du jeu à ajouter à la relation, et un bouton pour confirmer l’ajout.

Tant que le bouton de confirmation n’est pas cliqué, l’enregistrement n’existe pas dans la base de données.

Lorsque l’utilisateur confirme son ajout, une fonction ajouter_bis() est lancée, exécutant simplement :

valeur_1 = entree_1.get()
valeur_2 = entree_2.get()
valeur_3 = entree_3.get()
valeur_4 = entree_4.get()
#Id_Editeur récupéré par comme précédemment
data = (valeur_1, valeur_2, valeur_3, valeur_4, Id_Editeur)
curseur.execute("INSERT INTO JV (Jeu, Annee, Prix, Plateforme, Id_Editeur) VALUES (?,?,?,?,?)",data)

Ensuite, nous avons créé la fonction modifier(). Nous avons créé deux boutons permettant de modifier soit la relation Jeux Vidéo, soit la relation Editeurs.

Pour modifier la relation Editeur, on peut choisir l’éditeur à modifier soit par son Id_Editeur, soit par son nom actuel grâce à un bouton déroulant. Nous avons donc créé ce bouton déroulant et deux entrées correspondant d’abord au nouveau nom de l’éditeur, et ensuite à l’editeur à modifier. La commande SQL qui s’exécute est la suivante :

if bouton_attribut.get() == "Editeur"	
  curseur.execute("UPDATE ED SET Editeur = ? WHERE Editeur = ?",(entree_1.get(),entree_2.get()))
else :
  curseur.execute("UPDATE ED SET Editeur = ? WHERE Id_Editeur = ?",(entree_1.get(),entree_2.get())  

Si l’éditeur indiqué par son Id_Editeur ou son nom existe, alors l’enregistrement est modifié et un message de confirmation est affiché. Sinon il est indiqué que l’éditeur n’existe pas.

La fonction pour modifier un enregistrement de la relation principale est, dans les grandes lignes la même, mais l’utilisateur doit choisir l’attribut à modifier, avec un bouton déroulant. Ainsi :

curseur.execute("UPDATE ED SET Attribut_1 = ? WHERE Attribut_2 = ?",(entree_1.get(),entree_2.get())

« Attribut_1 » et « Attribut_2 » sont des valeurs fixes, testés par les boutons déroulants (demandant de nombreux tests « if » et « elif »)

Pour finir, il fallait pouvoir supprimer des éléments de la relation principale (la relation secondaire Editeurs ne peut que être lue ou modifiée). Deux attributs permettent d’identifier un jeu : son identifiant unique, et son nom (qui peut ne pas être unique). Pourtant nous avons choisi qu’il serait possible de supprimer un jeu grâce à son nom pour un utilisateur qui ne connaîtrait pas l’identifiant du jeu. Si le jeu indiqué par son Id_Jeu ou son nom existe, alors l’enregistrement est modifié et un message de confirmation est affiché. Sinon il est indiqué que le jeu n’existe pas.

Par la suite, quelque soit le choix de l’utilisateur, le fonction est le même : une entrée apparaît, dans laquelle il est possible de renseigner soit le jeu, soit l’id du jeu. Si le jeu indiqué par son Id_Jeu ou son nom existe, alors l’enregistrement est supprimé et un message de confirmation est affiché. Sinon il est indiqué que le jeu n’existe pas.

Nous avons donc créé les quatre fonctions pour interagir avec une base de données, soit la création, la lecture, la modification et la suppression de la base.

III. Conclusion

Ce compte-rendu touche maintenant à sa fin. N’hésitez pas à aller voir le tutoriel de Aël pour en apprendre plus sur la bibliothèque tkinter. Le fichier zip contenant tous les éléments nécessaires au bon fonctionnement du programme est disponible ci-dessous.

Projets

Agence de voyage et base de données

Une base de données sur des destinations paradisiaques tropicales. Consulter et réserver un voyage en exploitant une base de données dans python. L’interface utilisateur est gérée par tkinter

1 – Organiser les idées

Tout bon projet commence par un plan, afin de pouvoir poser et organiser l’idée de ce que l’on veut créer. Nous avons donc commencé par dessiner la base de notre projet :

2 – Création de nos tables

Une fois l’idée posée, il est temps de passer au programme !

Avant toute chose nous avons créé nos 4 tables et leurs attributs pour y ajouter après nos fichiers .csv créés à l’avance :

#création des tables
curseur.execute("CREATE TABLE IF NOT EXISTS DESTINATION (Id_Destination INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE, Nom TEXT, Continent TEXT, Temp_Moy INT, Note INT)")
curseur.execute("CREATE TABLE IF NOT EXISTS CLIENT(Id_Client INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE, Prenom TEXT, Nom TEXT, Lieu_depart TEXT)")
curseur.execute("CREATE TABLE IF NOT EXISTS VOL(Id_Vol INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE, Id_Destination INT, Lieu_Depart TEXT, Date_Loc_Depart DATE, Heure_Loc_Depart TIME, Temps_Vol TIME, Prix INT, FOREIGN KEY (Id_Destination) REFERENCES DESTINATION(Id_Destination), FOREIGN KEY (Lieu_Depart) REFERENCES CLIENT(Id_Client))")
curseur.execute("CREATE TABLE IF NOT EXISTS RESERVATION(Id_Reservation INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE, Id_Client INT, Id_Destination INT, Id_Vol INT, Num_Siege INT,FOREIGN KEY (Id_Client) REFERENCES CLIENT(Id_Client), FOREIGN KEY (Id_Destination) REFERENCES DESTINATION(Id_Destination), FOREIGN KEY (Id_Vol) REFERENCES VOL(Id_Vol))")
connection.commit()

Une fois les tables créées ont les a rempli avec les enregistrements contenus dans les fichiers .csv grâce au module panda:

#ajout des enregistrements .csv
pandas.read_csv("Client.csv").to_sql("CLIENT", connection, if_exists='replace', index=False)
pandas.read_csv("Destination.csv").to_sql("DESTINATION", connection, if_exists='replace', index=False)
pandas.read_csv("Vol.csv").to_sql("VOL", connection, if_exists='replace', index=False)
pandas.read_csv("Reservation.csv").to_sql("RESERVATION", connection, if_exists='replace', index=False)
connection.commit()

3 – La fenêtre d’accueil

A partir de là a commencé la création de la page d’accueil (dans les règles de l’art !)

Ici on définit l’aspect de notre fenêtre d’accueil, qui servira de modèle par la suite aux autres fenêtres.

#création de la fenêtre principale
window = Tk()
window.title("Agence de voyage : Partir un Jour sans Retour")
window.geometry("1080x720")
window.minsize(1080,775)
window.iconbitmap("island.ico")
window.config(background = "#B5D7F1")

Dans cette fenêtre on ajoute également 3 boutons, “destinations”, “réserver” et “supprimer son compte client” où les grandes actions s’effectuent.

4 – Fenêtre des destinations

Notre bouton destination mène donc à une fenêtre où les clients pourront découvrir les destinations proposées.

Les destinations sont d’abord triées par continent et rangées dans les boîtes correspondantes.

Ouvertes, ces boîtes affichent les destinations possibles du continent, rangées en tableau grâce à Treeview.

    tableau_europe = ttk.Treeview(boite_europe, columns=('Nom', 'Températures Moyennes', 'Note'), show = 'headings')
    tableau_europe.heading('Nom', text='Nom')
    tableau_europe.heading('Températures Moyennes', text='Températures Moyennes')
    tableau_europe.heading('Note', text='Note')
    
    for row in curseur.execute("SELECT Nom, Temp_Moy, Note FROM DESTINATION WHERE Continent = 'Europe' ORDER BY Nom"):
        tableau_europe.insert('', tk.END, values = row)
   
    tableau_europe.grid(row=0, column=0, sticky='nsew')

Nous avons ensuite utilisé Style() afin de personnaliser nos tableaux avec tous les attributs servant à les modifier comme sur l’image ci-dessous :

    #Style tableau europe
    
    tableau_europe = ttk.Style()
    #Prendre un theme
    tableau_europe.theme_use("clam")
    #configurer les couleurs du treeview
    tableau_europe.configure("Treeview",
        background = "#FEF3EA",
        foreground = "#206093",
        rowheight = 30,
        fieldbackground = "#FEF3EA"             
        )
    #Changer la couleur sélectionné par la souris
    tableau_europe.map('Treeview',
        background=[('selected', '#FFEE93')])

Ainsi qu’un bouton retour afin de fermer la boîte ouverte et de nouveau afficher les autres.

5 – Fenêtre réservation

Pour réserver un vol, il faut pouvoir choisir sa destination et s’inscrire dans la table Client.

Le plus simple a été de relier notre menu déroulant à la base de données. 

choix_destination = curseur.execute("SELECT Nom FROM DESTINATION")
    option_destination = OptionMenu(reservation, variable_destination, *choix_destination)
    option_destination.config(width=35, font=('Consolas', 12))

C’est au niveau du bouton de validation où cela s’est légèrement corsé.

Pas de soucis pour récupérer les textes entrés par l’utilisateur, ni son identifiant, qui constitueront son enregistrement dans la table Client.

        temp_client = (variable_prenom.get(), variable_nom.get())
        curseur.execute("INSERT INTO CLIENT(Prenom, Nom) VALUES (?,?)", temp_client)
        
        curseur.execute("SELECT MAX(Id_Client) FROM CLIENT")
        tempclient = curseur.fetchone()

C’est la récupération de l’identifiant de la destination qui aura posé le souci majeur puisque la donnée récupérée du menu déroulant se présentait comme un tuple sans en être un, enfin rien d’insurmontable un rien de bricolage suffit à régler le souci !

        temp_tempdestination = variable_destination.get()
        temp_bricodestination = (temp_tempdestination[2:-3],)
        curseur.execute("SELECT Id_Destination FROM DESTINATION WHERE Nom = ?", temp_bricodestination)
        temp_destination = curseur.fetchone()

On récupère également l’identifiant du vol

curseur.execute("SELECT Id_Vol FROM VOL WHERE Id_Destination = ?", temp_destination)
        temp_vol = curseur.fetchone()

Et une fois tous les attributs nécessaires à un enregistrement dans la relation réservation récupérés, on peut les y ajouter :

temp_reservation = (tempclient[0], temp_destination[0], temp_vol[0], randint(1, 600))
        curseur.execute("INSERT INTO RESERVATION(Id_Client, Id_Destination, Id_Vol, Num_Siege) VALUES (?,?,?,?)", temp_reservation)
        connection.commit()

6 – Fenêtre suppression de client

 Enfin si un client souhaite supprimer son compte nous avons la fenêtre “supprimer”.

Elle est composée d’une entrée utilisateur, où le client pourra renseigner son identifiant et d’un bouton “Valider” qui exécutera la suppression.

    def valider_suppression():
        temp_supprimer = variable_idclient.get()
        curseur.execute("DELETE FROM CLIENT WHERE Id_Client = ?", temp_supprimer)

Interface Tkinter réalisée avec l’aide du tutoriel d’Aël D.

Voici notre code compressé au format 7zip :

Découvrir

nsi42.net : Les projets web crées par nos élèves

Le site https://nsi42.net/ héberge les projets web de nos élèves. Codé par un ancien élève, ce site propose un jeu de piste à travers une interface en ligne de commande…

En classe de première

En classe de première, les élèves réalise un premier site web de quelques pages en février, et ils s’initient aux protocoles HTTP, HTTPS, FTP à travers ces projets.

C’est l’occasion de leur faire découvrir les langages HTML et CSS.

En classe de terminale

En classe de terminale, les élèves réalisent un premier site web dans lequel ils doivent intégrer des modules imposés et des modules librement choisis en octobre.

L’objectif est alors d’approfondir leurs connaissances en JAVASCRIPT.

Plus tard dans l’année, ils peuvent utiliser cet espace web pour mener à bien un projet centré sur les bases de données, et ce frotter ainsi aux langages PHP et SQL.

Découvrir le site nsi42.net 

Le site https://nsi42.net/ sera peut-être pour vous une découverte de l’ASCII Art et qui sait, peut-être que la console accepte les commandes Linux !