Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #définition de 2 classes : Interface_graphique() et Resultats_recherche()
- #raccourci entrée = rechercher, modifier
- #fenetres de confirmation pour suppression/modification
- #mode sans confirmation (case à cocher depuis la 1e fenetre)
- #appuyer sur echap, depuis n'importe quelle fenêtre, fait quitter tout le programme
- #après avoir modifié un individu, ce dernier reste en sélection dans l'affichage des résultats recherche
- file_name = "test.csv"
- #todo : l.215, l.239 (j'ai désactivé ces 2 lignes, elles buguent, j'y ai ajouté un mini descriptif)
- #todo : idées à la con : mettre en "avant-plan", implémenter raccourci ctrl+q & ctrl+w
- ##modules
- from tkinter import * # GUI
- from tkinter import messagebox
- from tkinter.ttk import * # Widgets avec thèmes
- import csv
- from random import choice, randint
- from string import ascii_lowercase
- from datetime import *
- ##liste_de_var
- #L'ensemble des variables de chaque fichier est représenté par une liste, dont chaque élément est une variable dudit fichier.
- #Cette variable est elle-même une liste, de la forme suivante :
- #["nom de la variable", "type de la variable", **paramètres supplémentaires, suivant le type de la variable**, "booleen:variable importante"]
- #Les différents types sont les suivants :
- #"d" : date ; on précisera en paramètre supplémentaire si la date est une date de début ("dd"),
- #si c'est une date de fin ("df"), ou si c'est une simple date ("d")
- #"int" : entier
- #"str" : chaîne de caractères
- #"ld" : liste déroulante
- #Par exemple, la variable "Situation de la demande" sera codée de la façon suivante :
- #["Situation de la demande", "ld", ["", "Active", "Annulée", "Fermée", "Pourvue"]]
- #Ici, le troisième élément de la liste représente donc les modalités de la variable.
- #En revanche, la variable "Nom du stagiaire" sera codée de la façon suivante :
- #["Nom du stagiaire", "str"]
- #Ici, il n'y a pas de paramètre supplémentaire à ajouter, la liste ne comprend donc que 2 éléments et non 3.
- Var_stage=[["Situation de la demande","ld",["","Active","Annulée","Fermée","Pourvue"],True],
- ["Nombre stagiaire","int",True],
- ["Nom du stagiaire","str",True],
- ["Prénom du stagiaire","str",True],
- ["Rémunération","ld",["","Oui","Non"],True],
- ["Site","ld",["","Paris","Arcueil","Angers"],True],
- ["Structure","str",True],
- ["Unité","str",True],
- ["Niveau études","ld",["","Collège","Lycée","Bac +1","Bac +2","Licence","Master 1","Master 2","Master Spécialisé"],True],
- ["Domaine études","str",True],
- ["Durée","int"],
- ["Date de début","d","dd",True],
- ["Date de fin","d","df",True],
- ["CHR","str",False],
- ["Indemnités de stage (brut)","int",False],
- ["N°TS","str",False],
- ["Nom responsable hierarchique","str",False],
- ["Nom tuteur","str",False],
- ["Demande validée","str",False],
- ["Transmission Dossier Paie/GA","str",False],
- ["Convention","str",False],
- ["Centre de formation","str",False],
- ["Adresse","str",False],
- ["Demande de badge","str",False],
- ["Commentaires","str",False]]
- ##ne pas appeler ce module csv
- def read_line(line_id):
- with open(file_name,'r') as myfile:
- r = csv.reader(myfile, delimiter=';', quotechar='"', lineterminator='\n')
- lines=[l for l in r]
- return lines[line_id]
- def add_line(new_line):
- "ajoute ligne à la fin du csv"
- #on lit le fichier en mode "append"
- with open(file_name,'a') as myfile:
- w = csv.writer(myfile, delimiter=';', quotechar='"', lineterminator='\n')
- w.writerow(new_line)
- return None
- def mod_line(line_id, new_line):
- "remplace la ligne d'indice line_id par new_line"
- #on lit le fichier en mémoire
- with open(file_name,'r') as myfile:
- r = csv.reader(myfile, delimiter=';', quotechar='"', lineterminator='\n')
- lines=[l for l in r]
- lines[line_id]=new_line
- # puis on le modifie (toujours en mémoire), avant d'en réécrire toutes les lignes.
- with open(file_name,'w') as myfile:
- w = csv.writer(myfile, delimiter=';', quotechar='"', lineterminator='\n')
- w.writerows(lines)
- return None
- def del_line(line_id):
- "supprime la ligne d'indice line_id"
- # on lit le fichier en mémoire
- with open(file_name,'r') as myfile:
- r = csv.reader(myfile, delimiter=';', quotechar='"', lineterminator='\n')
- lines=[l for l in r]
- lines.pop(line_id)
- # puis on le modifie (toujours en mémoire), avant d'en réécrire toutes les lignes.
- with open(file_name,'w') as myfile:
- w = csv.writer(myfile, delimiter=';', quotechar='"', lineterminator='\n')
- w.writerows(lines)
- return None
- ##outils de test
- def randomword(length):
- return ''.join(choice(ascii_lowercase) for i in range(length))
- def creer_exemples(n_indiv,liste_de_var):
- "retourne n_indiv individus : séquences 'relativement correctes' (de valeurs) stockées sous forme de listes à 25 éléments"
- L = []
- for i in range(n_indiv): #juste un bête *n_indiv
- indiv = []
- #cette boucle remplit indiv avec des valeurs suffisamment cohérentes pour faire tourner tout le programme
- for variable in liste_de_var: #(i+1)-eme variable[j] = Lvar_stage[i][j]
- if variable[1] == "str":
- indiv.append(randomword(8))
- elif variable[1] == "int":
- indiv.append(randint(1,180))
- elif variable[1] == "ld":
- indice_aleatoire = randint(0,len(variable[2])-1)
- indiv.append(variable[2][indice_aleatoire])
- elif variable[1] == "d":
- indiv.append(str(randint(1,28))+"/"+str(randint(1,12))+"/"+str(randint(2009,2017)))
- if not(datetime.strptime(indiv[11], '%d/%m/%Y')<datetime.strptime(indiv[12], '%d/%m/%Y')):
- indiv[12], indiv[11] = indiv[11], indiv[12]
- L.append(indiv)
- L.append(["exemple" for i in range(n)])
- return L
- def nouveau_fichier_test(n_indiv, liste_de_var):
- "crée 'test.csv' : données aléatoires d'individus"
- with open('test.csv','w') as file:
- writer = csv.writer(file, delimiter=";",lineterminator='\n')
- writer.writerow([liste_de_var[i][0] for i in range(n)])
- writer.writerows(creer_exemples(n_indiv, liste_de_var))
- return None
- ##main
- def est_inclus(str1, str2):
- "True si str1 est inclus dans str2, False sinon"
- diff = len(str2) - len(str1)
- if diff < 0:
- return False
- elif diff == 0:
- return (str1 == str2)
- else:
- return(est_inclus(str1,str2[1:]) or est_inclus(str1,str2[:-1]))
- def est_dans(elem_candidat,list):
- "True si elem_candidat est dans list, False sinon"
- reponse = False
- for elem in list:
- if elem == elem_candidat:
- reponse = True
- break
- return reponse
- def reponse_recherche(criteres):
- "Renvoie les résultats de la recherche"
- "Liste dont les éléments sont de la forme [[val1, ... , val25], line_id]"
- Lindiv_et_nligne = []
- with open(file_name,'r') as myfile:
- reader = csv.reader(myfile, delimiter=';', quotechar='"', lineterminator='\n')
- lines=[l for l in reader]
- for i in range(1,len(lines)): #on commence à 1 pour écarter la ligne des intitulés de variables
- for j in range(n): #j comptera presque le nombre de critères remplis
- if not(est_inclus(criteres[j],lines[i][j])) and (criteres[j] != ""): #(match critère ou critère inactif)
- j = 0
- break
- if j == n - 1:
- Lindiv_et_nligne.append([lines[i],i])
- return Lindiv_et_nligne
- def pop_up_confirmer():
- "renvoie True si l'utilisateur clique sur 'OK', False sinon"
- return messagebox.askokcancel("Demande de confirmation", "Êtes-vous sûr de vouloir continuer ?")
- def est_entier(s):
- "Verifier qu'un string est un entier ou est vide"
- if s == "":
- return True
- else:
- try:
- int(s)
- return True
- except Exception:
- return False
- def est_date(d):
- "Verifier qu'un string est soit vide soit un date sous la forme jour/mois/annee"
- if d == "":
- return True
- else:
- try:
- datetime.strptime(d, '%d/%m/%Y')
- return True
- except Exception:
- return False
- def controle_type(indiv, liste_de_var):
- "vérifie la cohérence des valeurs de l'individu 'indiv' conformément à la liste de variables 'liste_var'"
- Erreurs=[] #liste de booléens : Erreurs[i] vaut True s'il y a une erreur de saisie sur indiv[i], False sinon.
- dates_inversees = False #vaut True si la date de fin est antérieure à la date de début
- #on teste toutes les variables suivant leur type :
- for i in range(n):
- if est_dans(liste_de_var[i][1], ["str", "ld"]):
- Erreurs.append(False)
- elif liste_de_var[i][1]=="int":
- Erreurs.append(not(est_entier(indiv[i])))
- elif liste_de_var[i][1]=="d":
- Erreurs.append(not(est_date(indiv[i])))
- if liste_de_var[i][2]=="dd":
- date_debut=indiv[i]
- elif liste_de_var[i][2]=="df":
- date_fin=indiv[i]
- #on teste ensuite si la date de début est bien antérieure à la date de fin
- try:
- dates_inversees = datetime.strptime(date_debut, "%d/%m/%Y") > datetime.strptime(date_fin, "%d/%m/%Y")
- except ValueError:
- pass
- return (Erreurs,dates_inversees)
- def controle_est_vide(indiv, liste_de_var):
- "vérifie que les variables importantes ne sont pas vides"
- est_vide = [] #est_vide[i] = True si indiv[i] = "" (champ laissé vide)
- for i in range(n):
- if liste_de_var[i][-1]:
- est_vide.append(indiv[i] == "")
- elif not(liste_de_var[i][-1]):
- est_vide.append(False) #on considère que pour les variables non importantes, laisser vide leurs champs respectifs n'est pas une erreur
- return est_vide
- class Interface_graphique(Tk):
- """Affiche une interface graphique"""
- def __init__(self, which = "r", lower = None, line_id_a_modif = None, index_liste = None):
- """Interface de recherche ou interface de modification"""
- if which == "r" : #charge interface de recherche
- self.ig_rech()
- elif which == "a" : #charge interface d'ajout
- self.ig_ajout()
- elif which == "m" : #charge interface de modification
- self.ig_modif(lower, line_id_a_modif, index_liste)
- for i in range(n): #on a déjà grid les labels avec self.creer_Tkobjets()
- self.Tkobjets[i].grid(row=i,column=1)
- self.root.bind('<Return>', lambda e: self.raccourci_entree())
- self.root.mainloop()
- def creer_Tkobjets(self,liste_de_var):
- """Retourne les différents objets de saisie + grid les labels associes"""
- L = []
- for i in range(n):
- Label(self.root,text=liste_de_var[i][0]).grid(row=i,column=0,sticky=W)
- if liste_de_var[i][1]=="ld":
- L.append(Combobox(self.root,exportselection=0, width = 17 ,state="readonly"))
- L[i]["values"] = liste_de_var[i][2]
- L[i].set(liste_de_var[i][2][0])
- else: #on aurait pu utiliser elif est_dans(liste_de_var[i][1], ["str","int","d"]):
- L.append(Entry(self.root))
- return L
- def saisie(self): #fusion de criteres et saisie_modif
- """Renvoie valeurs saisies par l'utilisateur sous le format [val1, ... , val25]"""
- return [objet.get() for objet in self.Tkobjets]
- def afficher_erreur(self,erreur):
- """Affiche message d'erreur si saisie incohérente ; retourne True si erreur, False sinon"""
- msg = ""
- if erreur == "type":
- Erreurs, dates_inversees = controle_type(self.saisie(), liste_de_var)
- if dates_inversees: #on remet dans le bon sens
- val11 = self.Tkobjets[11].get()
- val12 = self.Tkobjets[12].get()
- self.Tkobjets[11].delete(0, END)
- self.Tkobjets[12].delete(0, END)
- self.Tkobjets[11].insert(0, val12)
- self.Tkobjets[12].insert(0, val11)
- elif erreur =="rempli":
- Erreurs = controle_est_vide(self.saisie(),liste_de_var)
- n_erreurs = sum(Erreurs) #Erreurs = [True/False, ... , True/False] avec : True = 1, False = 0
- if n_erreurs == 0:
- return False
- elif n_erreurs == 1:
- msg = "La variable " + liste_de_var[Erreurs.index(True)][0] + " est mal renseignée."
- elif n_erreurs > 1:
- msg_var = "" #stocke les noms des variables présentant des valeurs incohérentes
- for i in range(n):
- if Erreurs[i]:
- if n_erreurs > 1:
- msg_var = msg_var + " " + liste_de_var[i][0] + ","
- n_erreurs -= 1 #après cette ligne, il reste encore n_erreurs variables à ajouter à msg_var
- elif n_erreurs == 1: # il reste une seule variable
- msg_var = msg_var + " et " + liste_de_var[i][0]
- msg = "Les variables" + msg_var + " sont mal renseignées."
- if erreur == "rempli":
- n_erreurs = sum(Erreurs)
- if n_erreurs == 1:
- msg = msg + " (Cette variable ne peut être laissée vide)"
- elif n_erreurs > 1:
- msg = msg + " (Ces variables ne peuvent être laissées vides)"
- messagebox.showerror('Erreur',msg)
- return True
- def raccourci_entree(self):
- """Doit déterminer s'il faut lancer bouton_modif ou bouton_rech lorsque l'utilisateur appuie sur la touche entree"""
- if self.root.title() == "Saisie de recherche":
- self.bouton_rech()
- elif self.root.title() == "Saisie de modification":
- self.bouton_modif()
- return None
- ##interface de recherche
- def ig_rech(self):
- self.root = Tk()
- self.root.title("Saisie de recherche")
- self.root.bind('<Escape>', lambda e: self.root.destroy()) #s'execute lorsqu'on appuie sur Echap
- self.hardcore = IntVar() #création de "variable-retour" pour Checkbutton
- #Widgets (objets tkinter)
- self.Tkobjets = self.creer_Tkobjets(liste_de_var)
- Button(self.root, text="Rechercher",command = lambda : self.bouton_rech()).grid(row=0,column=3)
- Checkbutton(self.root, variable = self.hardcore, text = "Ne pas demander de confirmation").grid(row = 1, column = 3)
- return None
- def bouton_rech(self):
- #todo : self.root.withdraw() #fait disparaître self.root (et tous ses widgets) ; pour le faire réapparaître, utiliser self.deiconify() ; le pb : quand utiliser self.deiconify() ??
- erreur_types = self.afficher_erreur(erreur = "type")
- if not(erreur_types):
- Resultats_recherche(saisie_recherche = self)
- return None
- def elem_list(self):
- """Retourne iterable pour remplir la listbox de Resultats_recherche, recalcule resultats recherche"""
- a_afficher = reponse_recherche(self.saisie()) #est une liste dont les éléments sont de la forme [[val1, ... , val25], nligne]
- elem_list=[]
- for i in range(len(a_afficher)):
- elem="| "
- for j in range(n):
- elem=elem+a_afficher[i][0][j]+" | " # a_afficher[i][0][j] = val(j+1)
- elem_list.append(elem)
- return elem_list
- ##interface d'ajout
- def ig_ajout(self):
- self.root = Tk()
- self.root.title("Saisie d'ajout")
- self.root.bind('<Escape>', lambda e: self.root.destroy())
- self.hardcore = IntVar() #création de "variable-retour" pour Checkbutton
- #Widgets
- self.Tkobjets = self.creer_Tkobjets(liste_de_var)
- Checkbutton(self.root, variable = self.hardcore, text = "Ne pas demander de confirmation").grid(row = 1, column = 3)
- Button(self.root, text="Ajouter",command = lambda : self.bouton_ajout()).grid(row=0,column=3)
- return None
- def bouton_ajout(self):
- """Si confirmation, rend effectif la saisie de l'utilisateur"""
- erreur_rempli = self.afficher_erreur(erreur = "rempli")
- if not(erreur_rempli):
- erreur_types = self.afficher_erreur(erreur = "type")
- if not(erreur_types):
- if self.hardcore.get() or pop_up_confirmer():
- add_line(self.saisie())
- return None
- ##interface de modification
- def ig_modif(self, lower, line_id_a_modif, index_liste):
- self.t1 = lower #premier = deuxieme.t0 = troisieme.t1.t0 | deuxieme = troisieme.t1
- self.line_id_a_modif = line_id_a_modif
- self.index_liste = index_liste
- self.root = Toplevel()
- self.root.title("Saisie de modification")
- self.root.bind('<Escape>', lambda e: self.t1.t0.root.destroy())
- #todo : self.protocol("WM_DELETE_WINDOW", self.confirmer_sortie()) #permet de lancer self.confirmer_sortie() lorsqu'une fenêtre se ferme ; le pb c'est qu'elles se ferment toutes fréquemment
- #Widgets
- self.Tkobjets = self.creer_Tkobjets(liste_de_var)
- self.preremplir_formulaire_modif() #charge les données du csv quant à l'individu ciblé
- Button(self.root, text="Modifier",command = lambda : self.bouton_modif()).grid(row=0,column=3)
- return None
- def bouton_modif(self):
- """Si confirmation, rend effectif la saisie de l'utilisateur"""
- erreur_rempli = self.afficher_erreur(erreur = "rempli")
- if not(erreur_rempli):
- erreur_types = self.afficher_erreur(erreur = "type")
- if not(erreur_types):
- if self.t1.t0.hardcore.get() or pop_up_confirmer():
- mod_line(self.line_id_a_modif, self.saisie())
- self.t1.update_liste()
- self.t1.liste.selection_set(self.index_liste)
- return None
- def preremplir_formulaire_modif(self):
- """Preremplit avec valeurs du csv"""
- ligne_select = read_line(self.line_id_a_modif)
- for i in range(n):
- if liste_de_var[i][1]=="ld":
- for item in liste_de_var[i][2]:
- if est_dans(ligne_select[i],liste_de_var[i][2]): #pour éviter bug (sur listbox) si données mal renseignées dans le csv
- self.Tkobjets[i].set(ligne_select[i]) #en argt : indice voulu
- else:
- self.Tkobjets[i].insert(0,ligne_select[i]) #on fixe à la valeur du csv
- return None
- def confirmer_sortie(self):
- if self.saisie() != read_line(self.line_id_a_modif):
- messagebox.askokcancel("Confirmation", "Êtes-vous sûr de vouloir quitter sans enregistrer les modifications ?")
- class Resultats_recherche(Tk):
- """Affiche les resultats de la recherche dans une listbox"""
- def __init__(self, saisie_recherche):
- "Listbox + 2 boutons si resultats, messagebox sinon"
- self.t0 = saisie_recherche
- if self.t0.elem_list() != []: #listbox etc ne se lancent que si des individus correspondent à la requête
- self.root = Toplevel()
- self.root.title("Résultats de la recherche")
- self.liste = self.remplir_listbox()
- self.liste.pack()
- Button(self.root, text="Modifier",command=lambda:self.bouton_affiche_saisie_modif()).pack()
- Button(self.root, text="Supprimer",command=lambda:self.bouton_suppr()).pack()
- self.root.bind('<Escape>', lambda e: self.t0.root.destroy())
- self.root.mainloop()
- else:
- messagebox.showinfo("Résultats de la recherche","Aucun individu ne répond à la requête")
- def remplir_listbox(self):
- """Cree la listbox"""
- elem_list = self.t0.elem_list()
- listbox = Listbox(self.root, height=len(elem_list), width = max(len(ligne) for ligne in elem_list), exportselection=0)
- for item in elem_list:
- listbox.insert(END,item)
- return listbox
- def line_id(self):
- """Renvoie line_id dans csv de la selection"""
- choisi = self.liste.get(ACTIVE)
- n0 = self.t0.elem_list().index(choisi) #indice dans la listbox de la sélection
- line_id = reponse_recherche(self.t0.saisie())[n0][1] #indice dans le csv
- return line_id
- def index_liste(self):
- """Renvoie index dans la liste de la selection"""
- return self.liste.curselection()
- def bouton_affiche_saisie_modif(self):
- """Ouvre interface graphique pour modifier individu selectionne et renvoie line_id, index_liste"""
- Interface_graphique(which = "m", lower = self, line_id_a_modif = self.line_id(), index_liste = self.index_liste())
- return None
- def bouton_suppr(self):
- """Si confirmation, supprime la selection de la listbox"""
- if self.t0.hardcore.get() or pop_up_confirmer():
- del_line(self.line_id())
- self.update_liste()
- return None
- def update_liste(self):
- """Actualise la listbox"""
- elem_list = self.t0.elem_list()
- self.liste.delete(0,END)
- for item in elem_list:
- self.liste.insert(END,item)
- return None
- def fermeture_imminente(self):
- if messagebox.askokcancel("Quitter", "Voulez-vous quitter ?"):
- self.t0.root.destroy()
- return None
- ##exécution
- file_name = "test.csv"
- liste_de_var, n = Var_stage, len(Var_stage)
- nouveau_fichier_test(30,liste_de_var)
- Interface_graphique(which = "r")
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement