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

337 lines
18 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 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.lower(), 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'}")
self.dimensionsFenetre(enfant, 330, 220)
def ___verification():
"""Vérifie si les champs renseignées sont valides."""
# vérification pour le nom d'utilisateur
pass
# vérification pour le mot de passe
pass
# vérification pour le métier
pass
# vérification pour le nom
pass
# vérification pour le prénom
pass
# vérification pour la date de naissance
pass
# vérification pour l'adresse
pass
# vérification pour le code postal
pass
# Champs
Label(enfant, text="Nom d'utilisateur :").grid(column=0, row=0, sticky='e')
pseudo = Entry(enfant)
pseudo.grid(column=1, row=0, sticky='w')
Label(enfant, text="Mot de passe :").grid(column=0, row=1, sticky='e')
passe = Entry(enfant)
passe.grid(column=1, row=1, sticky='w')
Label(enfant, text="Métier :").grid(column=0, row=2, sticky='e')
metier = Entry(enfant)
metier.grid(column=1, row=2, sticky='w')
Label(enfant, text="Nom :").grid(column=0, row=3, sticky='e')
nom = Entry(enfant)
nom.grid(column=1, row=3, sticky='w')
Label(enfant, text="Prénom :").grid(column=0, row=4, sticky='e')
prenom = Entry(enfant)
prenom.grid(column=1, row=4, sticky='w')
Label(enfant, text="Date de naissance :").grid(column=0, row=5, sticky='e')
naissance = Entry(enfant)
naissance.grid(column=1, row=5, sticky='w')
Label(enfant, text="Adresse").grid(column=0, row=6, sticky='e')
adresse = Entry(enfant)
adresse.grid(column=1, row=6, sticky='w')
Label(enfant, text="Code postal :").grid(column=0, row=7, sticky='e')
postal = Entry(enfant)
postal.grid(column=1, row=7, 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`
enfant.title(f"{utilisateur['nom']} {utilisateur['prenom']}")
self.dimensionsFenetre(enfant, 300, 180)
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():
"""
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)
if not metier: # on ajoute tous les utilisateurs
for idx, utilisateur in enumerate(Utilisateurs().listUtilisateurs()):
listeUtilisateurs.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:
listeUtilisateurs.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:
listeUtilisateurs.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
# On ajoute nos utilisateurs à notre liste
__ajouterUtilisateursListe()
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=__ajouterUtilisateursListe).grid(sticky='w')
Checkbutton(filtres, text="Caissier", variable=caissierVerif, command=__ajouterUtilisateursListe).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()