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

434 lines
22 KiB
Python
Raw Normal View History

import tkinter.messagebox as messagebox
2021-11-20 18:09:34 +01:00
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:
2021-11-19 18:52:58 +01:00
"""Programme de Gestion d'une caise de magasin."""
def __init__(self) -> None:
"""Instancie quelques variables pour plus de clareté."""
2021-11-20 00:11:59 +01:00
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
2021-11-19 00:47:01 +01:00
self.parent.mainloop() # on affiche la fenêtre
2021-11-19 00:47:01 +01:00
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):
2021-11-19 00:47:01 +01:00
"""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
2021-11-19 00:47:01 +01:00
# 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.")
2021-11-20 11:39:16 +01:00
def dimensionsFenetre(self, fenetre, nouveauX: int, nouveauY: int):
2021-11-19 20:14:41 +01:00
"""Permet de changer les dimensions de la fenêtre parent et la place au centre de l'écran."""
2021-11-20 11:39:16 +01:00
largeur = fenetre.winfo_screenwidth()
hauteur = fenetre.winfo_screenheight()
x = (largeur // 2) - (nouveauX // 2)
y = (hauteur // 2) - (nouveauY // 2)
2021-11-20 11:39:16 +01:00
fenetre.geometry(f"{nouveauX}x{nouveauY}+{x}+{y}")
2021-11-19 00:47:01 +01:00
def _interfaceConnexion(self):
"""Affiche l'interface de connexion."""
# Paramètres de la fenêtre
2021-11-20 11:39:16 +01:00
self.dimensionsFenetre(self.parent, 400, 600)
2021-11-19 00:47:01 +01:00
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
2021-11-19 00:47:01 +01:00
self.f = Frame(self.parent)
self.f.grid()
# Affichage des labels et boutons
2021-11-19 00:47:01 +01:00
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é
2021-11-19 00:47:01 +01:00
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')
2021-11-19 00:47:01 +01:00
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')
2021-11-19 00:47:01 +01:00
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))
2021-11-19 00:47:01 +01:00
bouttonAffichageMDP = Button(self.f, text='👁', command=lambda: __afficherMDP(self))
bouttonAffichageMDP.grid(column=2, row=4, columnspan=2)
self.mdpVisible = False
2021-11-19 00:47:01 +01:00
bouton = Button(self.f, text="Se connecter", font=self.font, command=tentativeDeConnexion)
bouton.grid(column=0, row=5, columnspan=3, padx=ecart, pady=20)
2021-11-19 00:47:01 +01:00
bouton.bind("<Return>", tentativeDeConnexion)
2021-11-19 17:14:15 +01:00
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
2021-11-20 00:11:59 +01:00
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]}")
2021-11-20 11:39:16 +01:00
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']}")
2021-11-20 11:39:16 +01:00
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() # 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'}")
self.dimensionsFenetre(enfant, 300, 180)
Button(enfant, text="Quitter", command=enfant.destroy).grid(sticky='w')
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`
2021-11-20 11:39:16 +01:00
enfant.title(f"{utilisateur['nom']} {utilisateur['prenom']}")
utilisateur["metier"] = "Manager" if utilisateur["metier"] == 0 else "Caissier"
2021-11-20 12:02:48 +01:00
del utilisateur["passe"] # le manager ne doit pas connaître le mot de passe de l'utilisateur
2021-11-20 11:39:16 +01:00
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')
2021-11-20 12:02:48 +01:00
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
2021-11-20 18:09:34 +01:00
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():
"""
Ajoute des utilisateurs à la liste du Manager.
-> metier = 0 : manager uniquement
-> metier = 1 : caissier uniquement
-> metier = 2 : manager et caissier
"""
listeUtilisateurs.delete(0, "end") # vidé la liste des utilisateurs
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)
2021-11-21 02:38:01 +01:00
if metier == None: # on ajoute tous les utilisateurs
2021-11-20 18:09:34 +01:00
for idx, utilisateur in enumerate(Utilisateurs().listUtilisateurs()):
listeUtilisateurs.insert(idx, f"{utilisateur[0]} ({'manager' if utilisateur[1] == 0 else 'caissier'})")
2021-11-20 18:09:34 +01:00
elif metier == 0: # on ajoute que les managers
for idx, utilisateur in enumerate(Utilisateurs().listUtilisateurs()):
if utilisateur[1] == metier:
listeUtilisateurs.insert(idx, f"{utilisateur[0]} ({'manager' if utilisateur[1] == 0 else 'caissier'})")
2021-11-20 18:09:34 +01:00
elif metier == 1: # on ajoute que les caissiers
for idx, utilisateur in enumerate(Utilisateurs().listUtilisateurs()):
if utilisateur[1] == metier:
listeUtilisateurs.insert(idx, f"{utilisateur[0]} ({'manager' if utilisateur[1] == 0 else 'caissier'})")
2021-11-20 18:09:34 +01:00
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
2021-11-20 18:09:34 +01:00
__ajouterUtilisateursListe()
listeUtilisateurs.grid(column=0, row=5)
listeUtilisateurs.bind('<Double-Button>', __afficherInformationsUtilisateur) # on affiche l'utilisateur quand on double-clique dessus
2021-11-20 00:11:59 +01:00
2021-11-20 18:09:34 +01:00
# 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=__ajouterUtilisateursListe).grid(sticky='w')
Checkbutton(filtres, text="Caissier", variable=caissierVerif, command=__ajouterUtilisateursListe).grid(sticky='w')
if __name__ == "__main__":
2021-11-20 00:11:59 +01:00
"""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()