This repository has been archived on 2022-03-31. You can view files and clone it, but cannot push or open issues or pull requests.
GesMag/main.py

466 lines
25 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import tkinter.messagebox as messagebox
from tkinter import IntVar, Checkbutton, Scrollbar, Listbox, Entry, Button, Label, Frame, Tk, Toplevel
from re import sub
from users import Utilisateurs # import de mon fichier pour gérer la base de donnée
class GesMag:
"""Programme de Gestion d'une caise de magasin."""
def __init__(self) -> None:
"""Instancie quelques variables pour plus de clareté."""
self.parent = Tk() # fenêtre affiché à l'utilisateur
self.parent.resizable(False, False) # empêche la fenêtre d'être redimensionnée
self.f = Frame(self.parent) # `Frame` affiché à l'écran
def demarrer(self) -> None:
"""Lance le programme GesMag."""
self.font = ("Comfortaa", 14) # police par défaut
Utilisateurs().creationTable() # on créer la base de donnée si elle n'existe pas déjà
self._interfaceConnexion() # on créer la variable `self.f` qui est la frame a affiché
self.f.grid() # on affiche la frame
self.parent.mainloop() # on affiche la fenêtre
def motDePasseCorrect(self, motDPasse: str) -> tuple:
"""Détermine si un mot de passe suit la politique du programme ou non."""
if len(motDPasse) == 0: # si le champs est vide
return (False, "Mot de passe incorrect.")
if len(motDPasse) < 8: # si le mot de passe est plus petit que 8 caractères
return (False, "Un mot de passe doit faire 8 caractères minimum.")
"""
- Pour le regex, la fonction `sub` élimine tout ce qui est donné en fonction
du pattern renseigné, alors si la fonction `sub` renvoie pas exactement
la même chaîne de charactère alors c'est qu'il y avait un charactère interdit.
- J'utilises pas `match` parce que je suis plus à l'aise avec `sub`.
"""
if not sub(r"[A-Z]", '', motDPasse) != motDPasse:
return (False, "Un mot de passe doit au moins contenir une lettre majuscule.")
if not sub(r"[a-z]", '', motDPasse) != motDPasse:
return (False, "Un mot de passe doit au moins contenir une lettre minuscule.")
if not sub(r" *?[^\w\s]+", '', motDPasse) != motDPasse:
return (False, "Un mot de passe doit au moins contenir un caractère spécial.")
return (True,) # si aucun des tests précédents n'est valide, alors le mot de passe est valide
def utilisateurCorrect(self, utilisateur: str) -> tuple:
"""Détermine si un nom d'utilisateur suit la politique du programme ou non."""
"""
Pour le nom d'utilisateur on vérifie si le champs n'est pas vide
et si il y a bien que des lettres et des chiffres.
"""
if len(utilisateur) == 0:
return (False, "Utilisateur incorrect.")
if sub(r" *?[^\w\s]+", '', utilisateur) != utilisateur:
return (False, "Un nom d'utilisateur ne doit pas contenir de caractère spécial.")
return (True,)
def nomCorrect(self, nom: str) -> bool:
"""Détermine si un nom suit la politique du programme ou non."""
if len(nom) == 0:
return False
if sub(r" *?[^\w\s]+", '', nom) != nom: # pas de caractères spéciaux dans un nom
return False
return True
def prenomCorrect(self, prenom: str) -> bool:
"""Détermine si un prénom suit la politique du programme ou non."""
if len(prenom) == 0:
return False
if sub(r" *?[^\w\s]+", '', prenom) != prenom: # pas de caractères spéciaux dans un prénom
return False
return True
def naissanceCorrect(self, naissance: str) -> bool:
"""Détermine si une date de naissance suit la politique du programme ou non."""
if len(naissance) == 0:
return False
if sub(r"\d{4}\/\d{2}\/\d{2}", '', naissance) != '':
return False
return True
def adresseCorrect(self, adresse: str) -> bool:
"""Détermine si une adresse suit la politique du programme ou non."""
if len(adresse) == 0:
return False
return True
def postalCorrect(self, code: str) -> bool:
"""Détermine si un code postal suit la politique du programme ou non."""
if len(code) == 0:
return False
if sub(r"\d{5}", '', code) != '':
return False
return True
def connexion(self, utilisateur: str, motDePasse: str):
"""Gère la connexion aux différentes interfaces de l'application."""
"""
Vérification nom d'utilisateur / mot de passe correctement entré
avec leurs fonctions respectives.
"""
pseudoOk = self.utilisateurCorrect(utilisateur)
if not pseudoOk[0]:
messagebox.showerror("Erreur", pseudoOk[1])
return
mdpOk = self.motDePasseCorrect(motDePasse)
if not mdpOk[0]:
messagebox.showerror("Erreur", mdpOk[1])
return
# Redirection vers la bonne interface
utilisateurBaseDeDonnee = Utilisateurs().verificationIdentifiants(utilisateur, motDePasse)
if utilisateurBaseDeDonnee[0] > 0:
if utilisateurBaseDeDonnee[1] == 0: # si le métier est "Manager"
self._interfaceManager(utilisateurBaseDeDonnee[0])
elif utilisateurBaseDeDonnee[1] == 1: # si le métier est "Caissier"
self._interfaceCaissier(utilisateurBaseDeDonnee[0])
else:
messagebox.showerror("Erreur", "Une erreur est survenue : métier inconnue.")
else:
messagebox.showerror("Erreur", "Utilisateur ou mot de passe incorrect.")
def dimensionsFenetre(self, fenetre, nouveauX: int, nouveauY: int):
"""Permet de changer les dimensions de la fenêtre parent et la place au centre de l'écran."""
largeur = fenetre.winfo_screenwidth()
hauteur = fenetre.winfo_screenheight()
x = (largeur // 2) - (nouveauX // 2)
y = (hauteur // 2) - (nouveauY // 2)
fenetre.geometry(f"{nouveauX}x{nouveauY}+{x}+{y}")
def _interfaceConnexion(self):
"""Affiche l'interface de connexion."""
# Paramètres de la fenêtre
self.dimensionsFenetre(self.parent, 400, 600)
self.parent.title("Fenêtre de connexion")
# Suppresssion de la dernière Frame
self.f.destroy()
# Instanciation d'une nouvelle Frame, on va donc ajouter tout nos widgets à cet Frame
self.f = Frame(self.parent)
self.f.grid()
# Affichage des labels et boutons
tentativeDeConnexion = lambda _ = None: self.connexion(utilisateur.get(), motDpasse.get()) # lambda pour envoyer les informations entrés dans le formulaire
ecart = 80 # écart pour avoir un affichage centré
Label(self.f).grid(row=0, pady=50) # utilisé pour du padding (meilleur affichage)
Label(self.f, text="Utilisateur", font=self.font).grid(column=0, row=1, columnspan=2, padx=ecart - 20, pady=20, sticky='w')
utilisateur = Entry(self.f, font=self.font, width=18)
utilisateur.grid(column=1, row=2, columnspan=2, padx=ecart)
Label(self.f, text="Mot de passe", font=self.font).grid(column=0, row=3, columnspan=2, padx=ecart - 20, pady=20, sticky='w')
motDpasse = Entry(self.f, font=self.font, show='', width=18)
motDpasse.grid(column=1, row=4, columnspan=2, padx=ecart)
motDpasse.bind("<Return>", tentativeDeConnexion)
def __afficherMDP(self):
"""Permet de gérer l'affichage du mot de passe dans le champs sur la page de connexion."""
if self.mdpVisible == False: # si mot de passe caché, alors on l'affiche
self.mdpVisible = True
motDpasse.config(show='')
bouttonAffichageMDP.config(font=("Arial", 10, "overstrike"))
else: # inversement
self.mdpVisible = False
motDpasse.config(show='')
bouttonAffichageMDP.config(font=("Arial", 10))
bouttonAffichageMDP = Button(self.f, text='👁', command=lambda: __afficherMDP(self))
bouttonAffichageMDP.grid(column=2, row=4, columnspan=2)
self.mdpVisible = False
bouton = Button(self.f, text="Se connecter", font=self.font, command=tentativeDeConnexion)
bouton.grid(column=0, row=5, columnspan=3, padx=ecart, pady=20)
bouton.bind("<Return>", tentativeDeConnexion)
Button(self.f, text="Quitter", font=self.font, command=quit).grid(column=0, row=6, columnspan=4, pady=20)
self._interfaceManager(1) # DEBUG: affiche directement l'interface du Manager
def _interfaceCaissier(self, id: int):
"""Affiche l'interface du caissier."""
caissier = Utilisateurs().recuperationUtilisateur(id=id)[0]
self.parent.title(f"Caissier {caissier[3]} {caissier[4]}")
self.dimensionsFenetre(self.parent, 1280, 720)
# Suppresssion de la dernière Frame
self.f.destroy()
# Instanciation d'une nouvelle Frame, on va donc ajouter tout nos widgets à cet Frame
self.f = Frame(self.parent)
self.f.grid()
def _interfaceManager(self, id: int):
"""Affiche l'interface du manager."""
manager = Utilisateurs().recuperationUtilisateur(id=id)
self.parent.title(f"Manager {manager['nom']} {manager['prenom']}")
self.dimensionsFenetre(self.parent, 530, 720)
# Suppresssion de la dernière Frame
self.f.destroy()
# Instanciation d'une nouvelle Frame, on va donc ajouter tout nos widgets à cet Frame
self.f = Frame(self.parent)
self.f.grid()
Label(self.f, text="Interface Manager", font=(self.font[0], 20)).grid(column=0, row=0)
Button(self.f, text="Se déconnecter", font=self.font, command=self._interfaceConnexion).grid(column=1, row=0, padx=50)
Label(self.f).grid(row = 1, pady=10) # séparateur
def __ajouterUtilisateur(metier: int):
"""Permet de créer un nouvel utilisateur, manager (`metier = 0`) et caissier (`metier = 1`)."""
"""
L'enfant (`TopLevel`) dépend de la `Frame` et non du parent (`Tk`)
pour éviter de resté ouverte meme lorsque le manager se déconnecte.
"""
enfant = Toplevel(self.f)
enfant.title(f"Ajouter un {'manager' if metier == 0 else 'caissier'}")
def ___verification():
"""Vérifie si les champs renseignées sont valides."""
"""
La variable `ok` sert à savoir si la vérification est passée
si elle vaut `True` alors tout est bon,
Par contre si elle vaut `False` alors il y a eu une erreur.
Les valeurs `Entry` qui ne sont pas passés seront dans
la liste `mauvaisChamps`.
"""
ok = True
mauvaisChamps = []
# vérification pour le nom d'utilisateur
if self.utilisateurCorrect(pseudo.get())[0] == False or Utilisateurs().utilisateurExistant(pseudo.get()) == True:
ok = False
mauvaisChamps.append(pseudo)
# vérification pour le mot de passe
if self.motDePasseCorrect(passe.get())[0] == False:
ok = False
mauvaisChamps.append(passe)
# vérification pour le nom
if self.nomCorrect(nom.get()) == False:
ok = False
mauvaisChamps.append(nom)
# vérification pour le prénom
if self.prenomCorrect(prenom.get()) == False:
ok = False
mauvaisChamps.append(prenom)
# vérification pour la date de naissance
if self.naissanceCorrect(naissance.get()) == False:
ok = False
mauvaisChamps.append(naissance)
# vérification pour l'adresse
if self.adresseCorrect(adresse.get()) == False:
ok = False
mauvaisChamps.append(adresse)
# vérification pour le code postal
if self.postalCorrect(postal.get()) == False:
ok = False
mauvaisChamps.append(postal)
if ok == False:
"""
Tous les champs qui n'ont pas réunies les conditions nécéssaires
sont mis en orange pendant 3 secondes pour bien comprendre quelles champs
sont à modifié.
La fonction lambda `remettreCouleur` permet de remettre la couleur initial
après les 3 secondes.
"""
remettreCouleur = lambda widget, ancienneCouleur: widget.configure(bg=ancienneCouleur)
for champs in mauvaisChamps:
couleur = champs["background"] # couleur d'avant changement
champs.configure(bg="orange") # on change la couleur du champs en orange
# dans 3 secondes on fait : `remettreCouleur(champs, couleur)`
champs.after(3000, remettreCouleur, champs, couleur)
else:
# Tous les tests sont passés, on peut ajouter l'utilisateur à la base de donnée
Utilisateurs().ajoutUtilisateur(
pseudo.get(),
passe.get().strip(),
metier,
nom.get(),
prenom.get(),
naissance.get(),
adresse.get(),
int(postal.get()), # pas besoin de gérer d'erreur lors du cast car on a vérifié avant que c'était bien une suite de chiffre
)
__ajouterUtilisateursListe(listeUtilisateurs) # met à jour la liste
# Champs de saisie
# Nom d'utilisateurs
Label(enfant, text="Nom d'utilisateur :").grid(column=0, row=0, sticky='e')
Label(enfant, text="Pas de caractères spéciaux", font=("Arial", 10, "italic")).grid(column=2, row=0, sticky='w')
pseudo = Entry(enfant)
pseudo.grid(column=1, row=0, sticky='w')
# Mot de passe
Label(enfant, text="Mot de passe :").grid(column=0, row=1, sticky='e')
Label(enfant, text="1 majuscule, miniscule et caractère spécial minimum", font=("Arial", 10, "italic")).grid(column=2, row=1, sticky='w')
passe = Entry(enfant)
passe.grid(column=1, row=1, sticky='w')
# Nom
Label(enfant, text="Nom :").grid(column=0, row=2, sticky='e')
Label(enfant, text="Pas de caractères spéciaux", font=("Arial", 10, "italic")).grid(column=2, row=2, sticky='w')
nom = Entry(enfant)
nom.grid(column=1, row=2, sticky='w')
# Prénom
Label(enfant, text="Prénom :").grid(column=0, row=3, sticky='e')
Label(enfant, text="Pas de caractères spéciaux", font=("Arial", 10, "italic")).grid(column=2, row=3, sticky='w')
prenom = Entry(enfant)
prenom.grid(column=1, row=3, sticky='w')
# Date de naissance
Label(enfant, text="Date de naissance :").grid(column=0, row=4, sticky='e')
Label(enfant, text="Format : AAAA/MM/JJ", font=("Arial", 10, "italic")).grid(column=2, row=4, sticky='w')
naissance = Entry(enfant)
naissance.grid(column=1, row=4, sticky='w')
# Adresse
Label(enfant, text="Adresse").grid(column=0, row=5, sticky='e')
adresse = Entry(enfant)
adresse.grid(column=1, row=5, sticky='w')
# Code postal
Label(enfant, text="Code postal :").grid(column=0, row=6, sticky='e')
Label(enfant, text="5 chiffres", font=("Arial", 10, "italic")).grid(column=2, row=6, sticky='w')
postal = Entry(enfant)
postal.grid(column=1, row=6, sticky='w')
def ___viderChamps():
"""Vide tout les champs de leur contenu"""
# On récupère toutes les `Entry` de la fenêtre et on change leur contenu
for champ in [widget for type, widget in enfant.children.items() if "entry" in type]:
champ.delete(0, "end")
champ.update()
# Boutons
Button(enfant, text="Valider", command=___verification).grid(column=0, row=8, columnspan=3, sticky='w')
Button(enfant, text="Vider les champs", command=___viderChamps).grid(column=0, row=8, columnspan=3)
Button(enfant, text="Quitter", command=enfant.destroy).grid(column=0, row=8, columnspan=3, sticky='e')
def __retirerUtilisateur(metier: int):
"""Permet de supprimer un utilisateur existant, manager (`metier = 0`) et caissier (`metier = 1`)."""
enfant = Toplevel(self.f) # cf. l'explication dans `__ajouterUtilisateur`
enfant.title(f"Retirer un {'manager' if metier == 0 else 'caissier'}")
# Liste des utilisateurs
Label(enfant, text=f"Liste des {'manager' if metier == 0 else 'caissier'}", font=self.font).grid(column=0, row=0) # titre
# On définit une barre pour pouvoir scroller dans la liste
scroll_retirer = Scrollbar(enfant, orient="vertical")
scroll_retirer.grid(column=1, row=1, sticky="nse")
# On définit notre liste et on la lie à notre `Scrollbar`
listeUtilisateurs_retirer = Listbox(enfant, width=25, height=4, yscrollcommand=scroll_retirer.set)
scroll_retirer["command"] = listeUtilisateurs_retirer.yview # scroll à la verticale dans notre liste
# On ajoute nos utilisateurs à notre liste
__ajouterUtilisateursListe(listeUtilisateurs_retirer, metier)
listeUtilisateurs_retirer.grid(column=0, row=1)
# On affiche l'utilisateur quand on double-clique dessus
def ___suppressionUtilisateur():
"""Supprime l'utilisateur actuellement sélectionné dans la liste"""
element = listeUtilisateurs_retirer.curselection()
if len(element) == 0: # si aucun élément n'est selectionné
messagebox.showwarning("Attention", "Aucun utilisateur n'a été selectionné.")
else:
utilisateur = listeUtilisateurs_retirer.get(listeUtilisateurs_retirer.curselection()[0]).split('(')[0][:-1]
reponse = messagebox.askyesno("Confirmation", f"Voulez vous supprimer {utilisateur} ?")
if reponse == True:
Utilisateurs().suppressionUtilisateurs(utilisateur)
__ajouterUtilisateursListe(listeUtilisateurs_retirer) # met à jour la liste dans la fenêtre de suppression
__ajouterUtilisateursListe(listeUtilisateurs) # met à jour la liste dans l'interface principale
messagebox.showinfo("Information", f"Utilisateur {utilisateur} supprimé.")
# Boutons
Button(enfant, text="Supprimer", command=___suppressionUtilisateur).grid(column=0, row=8, columnspan=3, sticky='w')
Button(enfant, text="Quitter", command=enfant.destroy).grid(column=0, row=8, columnspan=3, sticky='e')
def __afficherInformationsUtilisateur(_):
"""Permet d'afficher les informations d'un utilisateur"""
element = listeUtilisateurs.curselection()
if len(element) == 0: # si aucun élément n'est selectionné
return
"""
On split le champs car dans la liste on affiche le métier entre
parenthèses et on doit donner que le nom d'utilisateur à
la fonction `recuperationUtilisateur`, aussi on retire le dernièr
charactère avec [:-1] car c'est un espace.
"""
utilisateur = Utilisateurs().recuperationUtilisateur(pseudo=listeUtilisateurs.get(element[0]).split('(')[0][:-1])
enfant = Toplevel(self.f) # cf. l'explication dans `__ajouterUtilisateur`
enfant.title(f"{utilisateur['nom']} {utilisateur['prenom']}")
utilisateur["metier"] = "Manager" if utilisateur["metier"] == 0 else "Caissier"
del utilisateur["passe"] # le manager ne doit pas connaître le mot de passe de l'utilisateur
for idx, cle in enumerate(utilisateur):
Label(enfant, text=f"{cle.capitalize()} :").grid(column=0, row=idx, sticky='e')
Label(enfant, text=utilisateur[cle]).grid(column=1, row=idx, sticky='w')
Button(enfant, text="Quitter", command=enfant.destroy).grid(column=3, row=0, sticky='w')
Button(self.f, text="Ajouter un caissier", font=self.font, command=lambda: __ajouterUtilisateur(1)).grid(column=0, row=2)
Button(self.f, text="Retirer un caissier", font=self.font, command=lambda: __retirerUtilisateur(1)).grid(column=1, row=2)
Label(self.f).grid(row = 3, pady=10) # séparateur
# Liste des utilisateurs
managerVerif = IntVar(self.f) # filtre pour afficher ou non les managers dans la liste
caissierVerif = IntVar(self.f) # filtre pour afficher ou non les caissiers ou non dans la liste
caissierVerif.set(1) # par défaut on affiche que les caissiers
def __ajouterUtilisateursListe(liste: Listbox, force: int = None):
"""
Ajoute des utilisateurs à la liste du Manager.
-> metier = 0 : manager uniquement
-> metier = 1 : caissier uniquement
-> metier = 2 : manager et caissier
"""
liste.delete(0, "end") # vidé la liste des utilisateurs
if force: # si `force` n'est pas `None`, alors on force l'utilisation d'un métier
metier = force
else: # sinon on fait une vérification normale en fonction des filtres de l'interface manager
if managerVerif.get() == 1:
if caissierVerif.get() == 1:
metier = None # on affiche les 2
else:
metier = 0 # on affiche seulement les managers
else:
metier = 1 # on affiche les caissiers
if caissierVerif.get() == 0: # rien est coché, on revient à la configuration par défaut (caissiers uniquement)
metier = 1
caissierVerif.set(1)
if metier == None: # on ajoute tous les utilisateurs
for idx, utilisateur in enumerate(Utilisateurs().listUtilisateurs()):
liste.insert(idx, f"{utilisateur[0]} ({'manager' if utilisateur[1] == 0 else 'caissier'})")
elif metier == 0: # on ajoute que les managers
for idx, utilisateur in enumerate(Utilisateurs().listUtilisateurs()):
if utilisateur[1] == metier:
liste.insert(idx, f"{utilisateur[0]} ({'manager' if utilisateur[1] == 0 else 'caissier'})")
elif metier == 1: # on ajoute que les caissiers
for idx, utilisateur in enumerate(Utilisateurs().listUtilisateurs()):
if utilisateur[1] == metier:
liste.insert(idx, f"{utilisateur[0]} ({'manager' if utilisateur[1] == 0 else 'caissier'})")
else: # ce cas est là au cas où mais n'est pas sensé être appellé
raise NameError("Métier inconnu.")
Label(self.f, text="Liste des utilisateurs", font=self.font).grid(column=0, row=4) # titre
# On définit une barre pour pouvoir scroller dans la liste
scroll = Scrollbar(self.f, orient="vertical")
scroll.grid(column=0, row=5, sticky="nse")
# On définit notre liste et on la lie à notre `Scrollbar`
listeUtilisateurs = Listbox(self.f, width=25, height=4, yscrollcommand=scroll.set)
scroll["command"] = listeUtilisateurs.yview # scroll à la verticale dans notre liste
# On ajoute nos utilisateurs à notre liste
__ajouterUtilisateursListe(listeUtilisateurs)
listeUtilisateurs.grid(column=0, row=5)
listeUtilisateurs.bind('<Double-Button>', __afficherInformationsUtilisateur) # on affiche l'utilisateur quand on double-clique dessus
# Filtre pour la liste
Label(self.f, text="Filtre", font=self.font).grid(column=1, row=4, sticky='w', padx=10) # titre
filtres = Frame(self.f) # Morceau qui va contenir nos checkbutton
filtres.grid(column=1, row=4, rowspan=2, sticky='w')
Checkbutton(filtres, text="Manager", variable=managerVerif, command=lambda: __ajouterUtilisateursListe(listeUtilisateurs)).grid(sticky='w')
Checkbutton(filtres, text="Caissier", variable=caissierVerif, command=lambda: __ajouterUtilisateursListe(listeUtilisateurs)).grid(sticky='w')
if __name__ == "__main__":
"""Application "GesMag" pour le module de Programmation d'interfaces (2021-2022)"""
print("-- Compte par défaut --\nNom d'utilisateur: admin\nMot de passe: P@ssword")
GesMag().demarrer()