Advertisement
Guest User

Untitled

a guest
Jan 16th, 2017
84
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 22.14 KB | None | 0 0
  1. #définition de 2 classes : Interface_graphique() et Resultats_recherche()
  2. #raccourci entrée = rechercher, modifier
  3. #fenetres de confirmation pour suppression/modification
  4. #mode sans confirmation (case à cocher depuis la 1e fenetre)
  5. #appuyer sur echap, depuis n'importe quelle fenêtre, fait quitter tout le programme
  6. #après avoir modifié un individu, ce dernier reste en sélection dans l'affichage des résultats recherche
  7. file_name = "test.csv"
  8. #todo : l.215, l.239 (j'ai désactivé ces 2 lignes, elles buguent, j'y ai ajouté un mini descriptif)
  9. #todo : idées à la con : mettre en "avant-plan", implémenter raccourci ctrl+q & ctrl+w
  10.  
  11. ##modules
  12. from tkinter import * # GUI
  13. from tkinter import messagebox
  14. from tkinter.ttk import * # Widgets avec thèmes
  15. import csv
  16. from random import choice, randint
  17. from string import ascii_lowercase
  18. from datetime import *
  19.  
  20. ##liste_de_var
  21. #L'ensemble des variables de chaque fichier est représenté par une liste, dont chaque élément est une variable dudit fichier.
  22. #Cette variable est elle-même une liste, de la forme suivante :
  23. #["nom de la variable", "type de la variable", **paramètres supplémentaires, suivant le type de la variable**, "booleen:variable importante"]
  24. #Les différents types sont les suivants :
  25.  
  26. #"d" : date ; on précisera en paramètre supplémentaire si la date est une date de début ("dd"),
  27. #si c'est une date de fin ("df"), ou si c'est une simple date ("d")
  28. #"int" : entier
  29. #"str" : chaîne de caractères
  30. #"ld" : liste déroulante
  31.  
  32. #Par exemple, la variable "Situation de la demande" sera codée de la façon suivante :
  33. #["Situation de la demande", "ld", ["", "Active", "Annulée", "Fermée", "Pourvue"]]
  34. #Ici, le troisième élément de la liste représente donc les modalités de la variable.
  35.  
  36. #En revanche, la variable "Nom du stagiaire" sera codée de la façon suivante :
  37. #["Nom du stagiaire", "str"]
  38. #Ici, il n'y a pas de paramètre supplémentaire à ajouter, la liste ne comprend donc que 2 éléments et non 3.
  39.  
  40.  
  41. Var_stage=[["Situation de la demande","ld",["","Active","Annulée","Fermée","Pourvue"],True],
  42. ["Nombre stagiaire","int",True],
  43. ["Nom du stagiaire","str",True],
  44. ["Prénom du stagiaire","str",True],
  45. ["Rémunération","ld",["","Oui","Non"],True],
  46. ["Site","ld",["","Paris","Arcueil","Angers"],True],
  47. ["Structure","str",True],
  48. ["Unité","str",True],
  49. ["Niveau études","ld",["","Collège","Lycée","Bac +1","Bac +2","Licence","Master 1","Master 2","Master Spécialisé"],True],
  50. ["Domaine études","str",True],
  51. ["Durée","int"],
  52. ["Date de début","d","dd",True],
  53. ["Date de fin","d","df",True],
  54. ["CHR","str",False],
  55. ["Indemnités de stage (brut)","int",False],
  56. ["N°TS","str",False],
  57. ["Nom responsable hierarchique","str",False],
  58. ["Nom tuteur","str",False],
  59. ["Demande validée","str",False],
  60. ["Transmission Dossier Paie/GA","str",False],
  61. ["Convention","str",False],
  62. ["Centre de formation","str",False],
  63. ["Adresse","str",False],
  64. ["Demande de badge","str",False],
  65. ["Commentaires","str",False]]
  66.  
  67.  
  68. ##ne pas appeler ce module csv
  69. def read_line(line_id):
  70. with open(file_name,'r') as myfile:
  71. r = csv.reader(myfile, delimiter=';', quotechar='"', lineterminator='\n')
  72. lines=[l for l in r]
  73. return lines[line_id]
  74.  
  75. def add_line(new_line):
  76. "ajoute ligne à la fin du csv"
  77. #on lit le fichier en mode "append"
  78. with open(file_name,'a') as myfile:
  79. w = csv.writer(myfile, delimiter=';', quotechar='"', lineterminator='\n')
  80. w.writerow(new_line)
  81. return None
  82.  
  83. def mod_line(line_id, new_line):
  84. "remplace la ligne d'indice line_id par new_line"
  85. #on lit le fichier en mémoire
  86. with open(file_name,'r') as myfile:
  87. r = csv.reader(myfile, delimiter=';', quotechar='"', lineterminator='\n')
  88. lines=[l for l in r]
  89. lines[line_id]=new_line
  90. # puis on le modifie (toujours en mémoire), avant d'en réécrire toutes les lignes.
  91. with open(file_name,'w') as myfile:
  92. w = csv.writer(myfile, delimiter=';', quotechar='"', lineterminator='\n')
  93. w.writerows(lines)
  94. return None
  95.  
  96. def del_line(line_id):
  97. "supprime la ligne d'indice line_id"
  98. # on lit le fichier en mémoire
  99. with open(file_name,'r') as myfile:
  100. r = csv.reader(myfile, delimiter=';', quotechar='"', lineterminator='\n')
  101. lines=[l for l in r]
  102. lines.pop(line_id)
  103. # puis on le modifie (toujours en mémoire), avant d'en réécrire toutes les lignes.
  104. with open(file_name,'w') as myfile:
  105. w = csv.writer(myfile, delimiter=';', quotechar='"', lineterminator='\n')
  106. w.writerows(lines)
  107. return None
  108.  
  109. ##outils de test
  110. def randomword(length):
  111. return ''.join(choice(ascii_lowercase) for i in range(length))
  112.  
  113. def creer_exemples(n_indiv,liste_de_var):
  114. "retourne n_indiv individus : séquences 'relativement correctes' (de valeurs) stockées sous forme de listes à 25 éléments"
  115. L = []
  116. for i in range(n_indiv): #juste un bête *n_indiv
  117. indiv = []
  118. #cette boucle remplit indiv avec des valeurs suffisamment cohérentes pour faire tourner tout le programme
  119. for variable in liste_de_var: #(i+1)-eme variable[j] = Lvar_stage[i][j]
  120. if variable[1] == "str":
  121. indiv.append(randomword(8))
  122. elif variable[1] == "int":
  123. indiv.append(randint(1,180))
  124. elif variable[1] == "ld":
  125. indice_aleatoire = randint(0,len(variable[2])-1)
  126. indiv.append(variable[2][indice_aleatoire])
  127. elif variable[1] == "d":
  128. indiv.append(str(randint(1,28))+"/"+str(randint(1,12))+"/"+str(randint(2009,2017)))
  129. if not(datetime.strptime(indiv[11], '%d/%m/%Y')<datetime.strptime(indiv[12], '%d/%m/%Y')):
  130. indiv[12], indiv[11] = indiv[11], indiv[12]
  131. L.append(indiv)
  132. L.append(["exemple" for i in range(n)])
  133. return L
  134.  
  135. def nouveau_fichier_test(n_indiv, liste_de_var):
  136. "crée 'test.csv' : données aléatoires d'individus"
  137. with open('test.csv','w') as file:
  138. writer = csv.writer(file, delimiter=";",lineterminator='\n')
  139. writer.writerow([liste_de_var[i][0] for i in range(n)])
  140. writer.writerows(creer_exemples(n_indiv, liste_de_var))
  141. return None
  142.  
  143. ##main
  144. def est_inclus(str1, str2):
  145. "True si str1 est inclus dans str2, False sinon"
  146. diff = len(str2) - len(str1)
  147. if diff < 0:
  148. return False
  149. elif diff == 0:
  150. return (str1 == str2)
  151. else:
  152. return(est_inclus(str1,str2[1:]) or est_inclus(str1,str2[:-1]))
  153.  
  154. def est_dans(elem_candidat,list):
  155. "True si elem_candidat est dans list, False sinon"
  156. reponse = False
  157. for elem in list:
  158. if elem == elem_candidat:
  159. reponse = True
  160. break
  161. return reponse
  162.  
  163. def reponse_recherche(criteres):
  164. "Renvoie les résultats de la recherche"
  165. "Liste dont les éléments sont de la forme [[val1, ... , val25], line_id]"
  166. Lindiv_et_nligne = []
  167. with open(file_name,'r') as myfile:
  168. reader = csv.reader(myfile, delimiter=';', quotechar='"', lineterminator='\n')
  169. lines=[l for l in reader]
  170. for i in range(1,len(lines)): #on commence à 1 pour écarter la ligne des intitulés de variables
  171. for j in range(n): #j comptera presque le nombre de critères remplis
  172. if not(est_inclus(criteres[j],lines[i][j])) and (criteres[j] != ""): #(match critère ou critère inactif)
  173. j = 0
  174. break
  175. if j == n - 1:
  176. Lindiv_et_nligne.append([lines[i],i])
  177. return Lindiv_et_nligne
  178.  
  179. def pop_up_confirmer():
  180. "renvoie True si l'utilisateur clique sur 'OK', False sinon"
  181. return messagebox.askokcancel("Demande de confirmation", "Êtes-vous sûr de vouloir continuer ?")
  182.  
  183. def est_entier(s):
  184. "Verifier qu'un string est un entier ou est vide"
  185. if s == "":
  186. return True
  187. else:
  188. try:
  189. int(s)
  190. return True
  191. except Exception:
  192. return False
  193.  
  194. def est_date(d):
  195. "Verifier qu'un string est soit vide soit un date sous la forme jour/mois/annee"
  196. if d == "":
  197. return True
  198. else:
  199. try:
  200. datetime.strptime(d, '%d/%m/%Y')
  201. return True
  202. except Exception:
  203. return False
  204.  
  205. def controle_type(indiv, liste_de_var):
  206. "vérifie la cohérence des valeurs de l'individu 'indiv' conformément à la liste de variables 'liste_var'"
  207. Erreurs=[] #liste de booléens : Erreurs[i] vaut True s'il y a une erreur de saisie sur indiv[i], False sinon.
  208. dates_inversees = False #vaut True si la date de fin est antérieure à la date de début
  209. #on teste toutes les variables suivant leur type :
  210. for i in range(n):
  211. if est_dans(liste_de_var[i][1], ["str", "ld"]):
  212. Erreurs.append(False)
  213. elif liste_de_var[i][1]=="int":
  214. Erreurs.append(not(est_entier(indiv[i])))
  215. elif liste_de_var[i][1]=="d":
  216. Erreurs.append(not(est_date(indiv[i])))
  217. if liste_de_var[i][2]=="dd":
  218. date_debut=indiv[i]
  219. elif liste_de_var[i][2]=="df":
  220. date_fin=indiv[i]
  221. #on teste ensuite si la date de début est bien antérieure à la date de fin
  222. try:
  223. dates_inversees = datetime.strptime(date_debut, "%d/%m/%Y") > datetime.strptime(date_fin, "%d/%m/%Y")
  224. except ValueError:
  225. pass
  226. return (Erreurs,dates_inversees)
  227.  
  228. def controle_est_vide(indiv, liste_de_var):
  229. "vérifie que les variables importantes ne sont pas vides"
  230. est_vide = [] #est_vide[i] = True si indiv[i] = "" (champ laissé vide)
  231. for i in range(n):
  232. if liste_de_var[i][-1]:
  233. est_vide.append(indiv[i] == "")
  234. elif not(liste_de_var[i][-1]):
  235. est_vide.append(False) #on considère que pour les variables non importantes, laisser vide leurs champs respectifs n'est pas une erreur
  236. return est_vide
  237.  
  238.  
  239. class Interface_graphique(Tk):
  240. """Affiche une interface graphique"""
  241.  
  242. def __init__(self, which = "r", lower = None, line_id_a_modif = None, index_liste = None):
  243. """Interface de recherche ou interface de modification"""
  244.  
  245. if which == "r" : #charge interface de recherche
  246. self.ig_rech()
  247. elif which == "a" : #charge interface d'ajout
  248. self.ig_ajout()
  249. elif which == "m" : #charge interface de modification
  250. self.ig_modif(lower, line_id_a_modif, index_liste)
  251.  
  252. for i in range(n): #on a déjà grid les labels avec self.creer_Tkobjets()
  253. self.Tkobjets[i].grid(row=i,column=1)
  254. self.root.bind('<Return>', lambda e: self.raccourci_entree())
  255. self.root.mainloop()
  256.  
  257. def creer_Tkobjets(self,liste_de_var):
  258. """Retourne les différents objets de saisie + grid les labels associes"""
  259. L = []
  260. for i in range(n):
  261. Label(self.root,text=liste_de_var[i][0]).grid(row=i,column=0,sticky=W)
  262. if liste_de_var[i][1]=="ld":
  263. L.append(Combobox(self.root,exportselection=0, width = 17 ,state="readonly"))
  264. L[i]["values"] = liste_de_var[i][2]
  265. L[i].set(liste_de_var[i][2][0])
  266. else: #on aurait pu utiliser elif est_dans(liste_de_var[i][1], ["str","int","d"]):
  267. L.append(Entry(self.root))
  268. return L
  269.  
  270. def saisie(self): #fusion de criteres et saisie_modif
  271. """Renvoie valeurs saisies par l'utilisateur sous le format [val1, ... , val25]"""
  272. return [objet.get() for objet in self.Tkobjets]
  273.  
  274. def afficher_erreur(self,erreur):
  275. """Affiche message d'erreur si saisie incohérente ; retourne True si erreur, False sinon"""
  276. msg = ""
  277. if erreur == "type":
  278. Erreurs, dates_inversees = controle_type(self.saisie(), liste_de_var)
  279. if dates_inversees: #on remet dans le bon sens
  280. val11 = self.Tkobjets[11].get()
  281. val12 = self.Tkobjets[12].get()
  282. self.Tkobjets[11].delete(0, END)
  283. self.Tkobjets[12].delete(0, END)
  284. self.Tkobjets[11].insert(0, val12)
  285. self.Tkobjets[12].insert(0, val11)
  286. elif erreur =="rempli":
  287. Erreurs = controle_est_vide(self.saisie(),liste_de_var)
  288. n_erreurs = sum(Erreurs) #Erreurs = [True/False, ... , True/False] avec : True = 1, False = 0
  289. if n_erreurs == 0:
  290. return False
  291. elif n_erreurs == 1:
  292. msg = "La variable " + liste_de_var[Erreurs.index(True)][0] + " est mal renseignée."
  293. elif n_erreurs > 1:
  294. msg_var = "" #stocke les noms des variables présentant des valeurs incohérentes
  295. for i in range(n):
  296. if Erreurs[i]:
  297. if n_erreurs > 1:
  298. msg_var = msg_var + " " + liste_de_var[i][0] + ","
  299. n_erreurs -= 1 #après cette ligne, il reste encore n_erreurs variables à ajouter à msg_var
  300. elif n_erreurs == 1: # il reste une seule variable
  301. msg_var = msg_var + " et " + liste_de_var[i][0]
  302. msg = "Les variables" + msg_var + " sont mal renseignées."
  303. if erreur == "rempli":
  304. n_erreurs = sum(Erreurs)
  305. if n_erreurs == 1:
  306. msg = msg + " (Cette variable ne peut être laissée vide)"
  307. elif n_erreurs > 1:
  308. msg = msg + " (Ces variables ne peuvent être laissées vides)"
  309. messagebox.showerror('Erreur',msg)
  310. return True
  311.  
  312. def raccourci_entree(self):
  313. """Doit déterminer s'il faut lancer bouton_modif ou bouton_rech lorsque l'utilisateur appuie sur la touche entree"""
  314. if self.root.title() == "Saisie de recherche":
  315. self.bouton_rech()
  316. elif self.root.title() == "Saisie de modification":
  317. self.bouton_modif()
  318. return None
  319.  
  320. ##interface de recherche
  321. def ig_rech(self):
  322. self.root = Tk()
  323. self.root.title("Saisie de recherche")
  324. self.root.bind('<Escape>', lambda e: self.root.destroy()) #s'execute lorsqu'on appuie sur Echap
  325. self.hardcore = IntVar() #création de "variable-retour" pour Checkbutton
  326. #Widgets (objets tkinter)
  327. self.Tkobjets = self.creer_Tkobjets(liste_de_var)
  328. Button(self.root, text="Rechercher",command = lambda : self.bouton_rech()).grid(row=0,column=3)
  329. Checkbutton(self.root, variable = self.hardcore, text = "Ne pas demander de confirmation").grid(row = 1, column = 3)
  330. return None
  331.  
  332. def bouton_rech(self):
  333. #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() ??
  334. erreur_types = self.afficher_erreur(erreur = "type")
  335. if not(erreur_types):
  336. Resultats_recherche(saisie_recherche = self)
  337. return None
  338.  
  339. def elem_list(self):
  340. """Retourne iterable pour remplir la listbox de Resultats_recherche, recalcule resultats recherche"""
  341. a_afficher = reponse_recherche(self.saisie()) #est une liste dont les éléments sont de la forme [[val1, ... , val25], nligne]
  342. elem_list=[]
  343. for i in range(len(a_afficher)):
  344. elem="| "
  345. for j in range(n):
  346. elem=elem+a_afficher[i][0][j]+" | " # a_afficher[i][0][j] = val(j+1)
  347. elem_list.append(elem)
  348. return elem_list
  349.  
  350. ##interface d'ajout
  351. def ig_ajout(self):
  352. self.root = Tk()
  353. self.root.title("Saisie d'ajout")
  354. self.root.bind('<Escape>', lambda e: self.root.destroy())
  355. self.hardcore = IntVar() #création de "variable-retour" pour Checkbutton
  356. #Widgets
  357. self.Tkobjets = self.creer_Tkobjets(liste_de_var)
  358. Checkbutton(self.root, variable = self.hardcore, text = "Ne pas demander de confirmation").grid(row = 1, column = 3)
  359. Button(self.root, text="Ajouter",command = lambda : self.bouton_ajout()).grid(row=0,column=3)
  360. return None
  361.  
  362. def bouton_ajout(self):
  363. """Si confirmation, rend effectif la saisie de l'utilisateur"""
  364. erreur_rempli = self.afficher_erreur(erreur = "rempli")
  365. if not(erreur_rempli):
  366. erreur_types = self.afficher_erreur(erreur = "type")
  367. if not(erreur_types):
  368. if self.hardcore.get() or pop_up_confirmer():
  369. add_line(self.saisie())
  370. return None
  371.  
  372. ##interface de modification
  373. def ig_modif(self, lower, line_id_a_modif, index_liste):
  374. self.t1 = lower #premier = deuxieme.t0 = troisieme.t1.t0 | deuxieme = troisieme.t1
  375. self.line_id_a_modif = line_id_a_modif
  376. self.index_liste = index_liste
  377. self.root = Toplevel()
  378. self.root.title("Saisie de modification")
  379. self.root.bind('<Escape>', lambda e: self.t1.t0.root.destroy())
  380. #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
  381. #Widgets
  382. self.Tkobjets = self.creer_Tkobjets(liste_de_var)
  383. self.preremplir_formulaire_modif() #charge les données du csv quant à l'individu ciblé
  384. Button(self.root, text="Modifier",command = lambda : self.bouton_modif()).grid(row=0,column=3)
  385. return None
  386.  
  387. def bouton_modif(self):
  388. """Si confirmation, rend effectif la saisie de l'utilisateur"""
  389. erreur_rempli = self.afficher_erreur(erreur = "rempli")
  390. if not(erreur_rempli):
  391. erreur_types = self.afficher_erreur(erreur = "type")
  392. if not(erreur_types):
  393. if self.t1.t0.hardcore.get() or pop_up_confirmer():
  394. mod_line(self.line_id_a_modif, self.saisie())
  395. self.t1.update_liste()
  396. self.t1.liste.selection_set(self.index_liste)
  397. return None
  398.  
  399. def preremplir_formulaire_modif(self):
  400. """Preremplit avec valeurs du csv"""
  401. ligne_select = read_line(self.line_id_a_modif)
  402. for i in range(n):
  403. if liste_de_var[i][1]=="ld":
  404. for item in liste_de_var[i][2]:
  405. 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
  406. self.Tkobjets[i].set(ligne_select[i]) #en argt : indice voulu
  407. else:
  408. self.Tkobjets[i].insert(0,ligne_select[i]) #on fixe à la valeur du csv
  409. return None
  410.  
  411. def confirmer_sortie(self):
  412. if self.saisie() != read_line(self.line_id_a_modif):
  413. messagebox.askokcancel("Confirmation", "Êtes-vous sûr de vouloir quitter sans enregistrer les modifications ?")
  414.  
  415.  
  416. class Resultats_recherche(Tk):
  417. """Affiche les resultats de la recherche dans une listbox"""
  418. def __init__(self, saisie_recherche):
  419. "Listbox + 2 boutons si resultats, messagebox sinon"
  420. self.t0 = saisie_recherche
  421. if self.t0.elem_list() != []: #listbox etc ne se lancent que si des individus correspondent à la requête
  422. self.root = Toplevel()
  423. self.root.title("Résultats de la recherche")
  424. self.liste = self.remplir_listbox()
  425. self.liste.pack()
  426. Button(self.root, text="Modifier",command=lambda:self.bouton_affiche_saisie_modif()).pack()
  427. Button(self.root, text="Supprimer",command=lambda:self.bouton_suppr()).pack()
  428. self.root.bind('<Escape>', lambda e: self.t0.root.destroy())
  429. self.root.mainloop()
  430. else:
  431. messagebox.showinfo("Résultats de la recherche","Aucun individu ne répond à la requête")
  432.  
  433. def remplir_listbox(self):
  434. """Cree la listbox"""
  435. elem_list = self.t0.elem_list()
  436. listbox = Listbox(self.root, height=len(elem_list), width = max(len(ligne) for ligne in elem_list), exportselection=0)
  437. for item in elem_list:
  438. listbox.insert(END,item)
  439. return listbox
  440.  
  441. def line_id(self):
  442. """Renvoie line_id dans csv de la selection"""
  443. choisi = self.liste.get(ACTIVE)
  444. n0 = self.t0.elem_list().index(choisi) #indice dans la listbox de la sélection
  445. line_id = reponse_recherche(self.t0.saisie())[n0][1] #indice dans le csv
  446. return line_id
  447.  
  448. def index_liste(self):
  449. """Renvoie index dans la liste de la selection"""
  450. return self.liste.curselection()
  451.  
  452. def bouton_affiche_saisie_modif(self):
  453. """Ouvre interface graphique pour modifier individu selectionne et renvoie line_id, index_liste"""
  454. Interface_graphique(which = "m", lower = self, line_id_a_modif = self.line_id(), index_liste = self.index_liste())
  455. return None
  456.  
  457. def bouton_suppr(self):
  458. """Si confirmation, supprime la selection de la listbox"""
  459. if self.t0.hardcore.get() or pop_up_confirmer():
  460. del_line(self.line_id())
  461. self.update_liste()
  462. return None
  463.  
  464. def update_liste(self):
  465. """Actualise la listbox"""
  466. elem_list = self.t0.elem_list()
  467. self.liste.delete(0,END)
  468. for item in elem_list:
  469. self.liste.insert(END,item)
  470. return None
  471.  
  472. def fermeture_imminente(self):
  473. if messagebox.askokcancel("Quitter", "Voulez-vous quitter ?"):
  474. self.t0.root.destroy()
  475. return None
  476.  
  477.  
  478.  
  479. ##exécution
  480. file_name = "test.csv"
  481. liste_de_var, n = Var_stage, len(Var_stage)
  482.  
  483. nouveau_fichier_test(30,liste_de_var)
  484. Interface_graphique(which = "r")
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement