]> git.immae.eu Git - perso/Denise/oms.git/blob - gestion_donnees.py
nettoyage du code + passage en "défaut" de la grille améliorée (améliorée). Correctio...
[perso/Denise/oms.git] / gestion_donnees.py
1 #!/usr/bin/env python3
2 # -*- coding: utf-8 -*-
3
4 from configuration import CONFIG,DEFAUT
5 from gestion_erreurs import debug, warning, erreur, initialise_erreurs
6 from gestion_couleurs import rgb_vers_tuple, tuple_vers_rgb
7 from gestion_unites import choix_unite
8 import datetime
9 import json
10 import unidecode
11 import copy
12
13 ### Les données "tournent" selon :
14 ### python -> json -> (export/import) -> formulaire HTML -> données POST -> python etc
15
16 ############ Fonctions de conversion
17
18 def convertit_jours_vers_python(chaine,liste_err):
19 """ convertit une chaine de type 1a 3m 1s 10j en jours
20 Renvoie un nombre de jours en float
21 Si un des caractères n'est ni un nombre, ni une lettre "autorisée" ni une espace,
22 on affiche un warning et on ignore ce caractère
23 """
24 # debug("conversion de "+chaine+" vers un nb de jours",liste_err)
25 chainenombre = ""
26 agejours = 0.
27 for lettre in chaine:
28 if lettre.isdigit():
29 chainenombre += lettre
30 else:
31 if lettre == 'a' or lettre == 'A':
32 # On a trouvé l'année, on ajoute tout ce qui est trouvé jusque là
33 agejours += int(chainenombre)*CONFIG["jours_dans_annee"]
34 chainenombre = ""
35 elif lettre == 'm' or lettre == 'M':
36 # On a trouvé le mois
37 agejours += int(chainenombre)*CONFIG["jours_dans_mois"]
38 chainenombre = ""
39 elif lettre == 's' or lettre == 'S':
40 # la semaine
41 agejours += int(chainenombre)*CONFIG["jours_dans_semaine"]
42 chainenombre = ""
43 elif lettre == 'j' or lettre == 'J':
44 # On a trouvé le jour
45 agejours += int(chainenombre)
46 chainenombre = ""
47 elif lettre != ' ':
48 # autre caractère : bizarre ?
49 warning("convertit_jour_vers_python : caractère invalide : "+lettre,liste_err)
50 # à la fin s'il reste qqch on le garde dans les jours
51 if chainenombre != "":
52 agejours += int(chainenombre)
53 if agejours<0:
54 warning("L'âge est négatif !",liste_err)
55 agejours = 0
56 # debug("On a convertit ! Résultat : "+str(agejours),liste_err)
57 return agejours
58
59 # python -> json
60 def convertit_age_vers_texte(nombre):
61 """ convertit un nombre de jours en un truc plus lisible en mois, années, jours
62 et renvoie une chaîne sous la forme 3a2m1j par exemple"""
63 annees = int(nombre / CONFIG["jours_dans_annee"])
64 restant = nombre - annees*CONFIG["jours_dans_annee"]
65 mois = int(restant/CONFIG["jours_dans_mois"])
66 jours= round(nombre - mois*CONFIG["jours_dans_mois"] - annees*CONFIG["jours_dans_annee"])
67
68 chaine = ""
69 if annees >0:
70 chaine += str(annees)+"a"
71 if mois >0:
72 chaine += str(mois)+"m"
73 if jours>0 or nombre ==0: # si c'est la naissance, faut beien écrire 0j quand même
74 chaine += str(jours)+"j"
75 return chaine
76
77 ##########################
78
79 # fonction qui calcule "auto" le maxi du graphique en fonction du max
80 def calcule_max_graphique(l_jours):
81 """ calcule l'age maxi sur le graphique"""
82 if l_jours == []:
83 return CONFIG["jours_defaut_donneesvides"]
84 else:
85 jour_maxi = max(l_jours)# pas la peine d'aller très au delà du jour max
86 jour_maxi = int(jour_maxi* 1.2)+3 # on rajoute un peu
87 return jour_maxi
88
89
90 def simplifie_nom(chaine):
91 """ simplifie le nom chaine afin d'en faire une extension
92 pour le nom du fichier. Met tout en minuscules et vire les caractères spéciaux
93 et max 15 caractères"""
94 chaine2 = ""
95 for l in chaine:
96 if l.isalpha():
97 chaine2+=l
98 chaine2 = unidecode.unidecode(chaine2)
99 return chaine2[:15]
100
101 def convertit_donnee_vers_python(chaine,typedonnee,liste_err):
102 """ convertit une chaine vers un float qui est le type donnee voulu.
103 La virgule peut être . ou , et on vire d'éventuels espaces.
104 Taille invalide : on renvoie 0 avec un warning."""
105 chaine2 = chaine.replace(",",".")
106 chaine2 = chaine2.replace(" ","")
107
108 try:
109 donnee = float(chaine2)
110 except:
111 warning(typedonnee+" impossible à lire : "+chaine,liste_err)
112 donnee = 0
113 if not( 0<=donnee<CONFIG[typedonnee+"_maxi"]):
114 warning(typedonnee+"incohérent(e) : "+str(donnee),liste_err)
115 donnee = 0
116 return donnee
117
118
119 #########################
120
121 # web -> python
122 def convertit_date_vers_python(chaine,liste_err):
123 """ prend une chaine comme renvoyée par un champ de formulaire date
124 aaaa-mm-jj et en fait une date python
125 renvoie "" si ne marche pas"""
126 liste = chaine.split("-")
127 if len(liste) != 3:
128 warning("La date : "+chaine+" est invalide !",liste_err)
129 return ""
130 else:
131 debug("Conversion de la date "+chaine+". Découpage : "+str(liste),liste_err)
132 try:
133 date = datetime.date(int(liste[0]),int(liste[1]),int(liste[2]))
134 except:
135 date = ""
136 warning("Impossible de lire la date "+chaine+". Format accepté : aaaa-mm-jj",liste_err)
137 return date
138
139 # python -> json
140 def convertit_date_vers_texte(date):
141 """ convertit une date python en format texte aaaa-mm-jj"""
142 if date == "":
143 return ""
144 else:
145 return (str(date.year)+"-"+str(date.month)+"-"+str(date.day))
146
147
148 def delta_date(date1,datenaissance):
149 """ renvoie le nombre de jours (entier) entre date1 et datenaissance format "datetime"
150 datenaissance est supposée antérieure. Erreur sinon."""
151 d = date1 - datenaissance
152 jours = d.days
153 if jours<0:
154 warning("La différence entre les dates est négative... :/")
155 return -1
156 return jours
157
158
159 ################### On regroupe tout ce qui gère les données en une fonction
160
161 def web_vers_python(data,liste_err):
162 """ prend en argument le dictionnaire de requête et renvoie la config, et les
163 tableaux de donnée"""
164
165 # Régler la configuration
166 config = gere_configuration(data,liste_err)
167
168 # récupérer les données
169 listes_jours = {}
170 listes_donnees = {}
171 for typed in CONFIG["liste_typedonnees"]:
172 listes_jours[typed],listes_donnees[typed] = gere_donnees(data,config["naissance"],typed,liste_err)
173
174 # Si on a choisi la même échelle de données
175 if config["memechelle"] == "oui":
176 config["non_sauve"]["maxi"] = calcule_max_graphique([j for lj in listes_jours.values() for j in lj])
177 config["non_sauve"]["unite"] = choix_unite(config["non_sauve"]["maxi"])
178
179 return (config,listes_jours,listes_donnees)
180
181
182
183 ########### Fonctions qui gèretn les données web vers python
184
185 def gere_checkbox(chaine):
186 """ prend en arg une chaine, et renvoie "oui" si c'est "on" (sortie de la checkbox)
187 et chaîne vide si n'importe quoi d'autre"""
188 if chaine == "on":
189 return "oui"
190 else:
191 return ""
192
193 def gere_configuration(data,liste_err):
194 """ prend en argument le dictionnaire de requête (configuration imparfaite), et
195 construit le dictionnaire de configuration qui va bien.
196 Vérifie que chaque entrée est cohérente évidemment."""
197 # Initialisation
198 configuration = {"non_sauve": {}}
199
200 # Pour le nom, osef qu'il soit vide
201 nom = data.get("nom","")
202 # Par contre s'il est trop long on le tronque
203 configuration["nom"] = nom[:CONFIG["longueur_max_nom_bebe"]]
204
205 sexe = data.get("sexe","")
206 if not (sexe in ["F","M","N"]):
207 warning("Le sexe de l'enfant est invalide ! "+sexe,liste_err)
208 sexe = "N"
209 configuration["sexe"] = sexe
210
211 naissance = data.get("naissance","")
212 if naissance !="":
213 naissance = convertit_date_vers_python(naissance,liste_err)
214 configuration["naissance"] = naissance
215
216 prematurite = data.get("prematurite","")
217 j = convertit_jours_vers_python(prematurite,liste_err)
218 configuration["prematurite"] = convertit_age_vers_texte(j)
219
220 configuration["agecorrige"] = gere_checkbox(data.get("agecorrige",""))
221
222 # Type de courbe. Au pire on met P
223 tyc = data.get("typecourbe","")
224 if not (tyc in ["P","Z"]):
225 tyc = "P"
226 configuration["typecourbe"] = tyc
227
228 # unité
229 unite = data.get("unite","")
230 if not (unite in CONFIG["liste_unites"]):
231 unite = ""
232 #warning("L'unité "+unite+" n'est pas reconnue !",liste_err)
233 configuration["unite"] = unite
234
235 # grille
236 configuration["grille"] = gere_checkbox(data.get("grille",""))
237
238 # tracer ou non les courbes vides
239 configuration["tracevide"] = gere_checkbox(data.get("tracevide",""))
240
241 # Même échelle sur tous les graphiques
242 configuration["memechelle"] = gere_checkbox(data.get("memechelle",""))
243
244
245 # maxi. 0 signifie qu'on veut pas de maxi
246 maxi = data.get("maxi","")
247 if maxi == "":
248 configuration["maxi"] = 0
249 else:
250 configuration["maxi"] = int(convertit_jours_vers_python(maxi,liste_err))
251
252 # dimensions du graphique
253 largeur = data.get("largeur","")
254 if largeur == "":
255 largeur = DEFAUT["largeur_graphique"]
256 else:
257 try:
258 largeur = int(largeur)
259 except:
260 warning("La largeur "+largeur+"est invalide !",liste_err)
261 largeur = DEFAUT["largeur_graphique"]
262 if largeur > CONFIG["largeur_graphique_max"]:
263 largeur = CONFIG["largeur_graphique_max"]
264 warning("Largeur du graphique trop grande !",liste_err)
265 elif largeur < CONFIG["largeur_graphique_min"]:
266 largeur = CONFIG["largeur_graphique_min"]
267 warning("Largeur du graphique trop petite !",liste_err)
268 configuration["largeur"] = largeur
269
270 hauteur = data.get("hauteur","")
271 if hauteur == "":
272 hauteur = DEFAUT["hauteur_graphique"]
273 else:
274 try:
275 hauteur = int(hauteur)
276 except:
277 warning("La hauteur "+hauteur+"est invalide !",liste_err)
278 hauteur = DEFAUT["hauteur_graphique"]
279 if hauteur > CONFIG["hauteur_graphique_max"]:
280 hauteur = CONFIG["hauteur_graphique_max"]
281 warning("Hauteur du graphique trop grande !",liste_err)
282 elif hauteur < CONFIG["hauteur_graphique_min"]:
283 hauteur = CONFIG["hauteur_graphique_min"]
284 warning("Hauteur du graphique trop petite !",liste_err)
285 configuration["hauteur"] = hauteur
286
287 # existence et position de la légende
288 configuration["legende"] = gere_checkbox(data.get("legende",""))
289
290 positionlegende = data.get("positionlegende","")
291 if not(positionlegende in ['upper left','upper right','lower left','lower right']):
292 positionlegende = "upper left"
293 configuration["positionlegende"] = positionlegende
294
295 configuration["prolongercourbes"] = gere_checkbox(data.get("prolongercourbes",""))
296
297 # Ceci n'a pas besoin d'être sauvé
298 configuration["non_sauve"]["grilleamelio"] = gere_checkbox(data.get("grilleamelio",""))
299
300 configuration["couleurs"] = {}
301 # gérer les couleurs
302 for clecouleur in DEFAUT["couleurs"]:
303 coul = rgb_vers_tuple(data.get("couleur_"+clecouleur,""),CONFIG["couleurs"][clecouleur],liste_err)
304 configuration["couleurs"][clecouleur] = coul
305
306
307 return configuration
308
309 ## web vers python : données
310 def gere_donnees(data,naissance,typedonnee,liste_err):
311 """ prend en argument le dictionnaire de requête, et la date de
312 naissance (éventuellement vide), et construit deux listes :
313 l_jours et l_data correspondantes.
314 Il faut donner en argument le type de données : voir
315 CONFIG["liste_typedonnees"]"""
316 if typedonnee not in CONFIG["liste_typedonnees"]:
317 warning("gere_donnees : le type de données : "+typedonnee+" est invalide !! Types acceptés : "+str(CONFIG["liste_typedonnees"]),liste_err)
318 return ([],[])
319
320 # On construit une liste de couples d'abord
321 liste_donnees = []
322
323 i = 0
324 # On va chercher si y'a des données à donnee_i
325 while typedonnee+"_"+str(i) in data.keys():
326 if data[typedonnee+"_"+str(i)] != "":
327 donnee = convertit_donnee_vers_python(data[typedonnee+"_"+str(i)],typedonnee,liste_err)
328 age = data.get("age_"+str(i),"")
329 if age !="":
330 age = convertit_jours_vers_python(age,liste_err)
331 liste_donnees.append((age,donnee))
332 else:
333 date = data.get("date_"+str(i),"")
334 datep = convertit_date_vers_python(date,liste_err)
335 # on vérifie la date
336 if naissance == "":
337 warning("La date de naissance n'a pas été précisée. Du coup on ne peut pas calculer l'âge de l'enfant le "+date,liste_err)
338 elif datep != "": # la date est valide et on a une date de naissance
339 age = delta_date(datep,naissance)
340 liste_donnees.append((age,donnee))
341 i+=1
342
343 # Trier la liste
344 liste_donnees.sort(key=lambda x : x[0])
345
346 # splitter la liste
347 l_jours = [x[0] for x in liste_donnees]
348 l_donnee = [x[1] for x in liste_donnees]
349
350 return (l_jours,l_donnee)
351
352
353 # python vers Json
354 #### export vers json
355
356 def donnees_vers_json(l_jours,l_poids,l_jourst,l_taille,config):
357 """ retourne le json à renvoyer"""
358 gros_dico = copy.deepcopy(config)
359 l_jours2 = [convertit_age_vers_texte(d) for d in l_jours]
360 l_jourst2 = [convertit_age_vers_texte(d) for d in l_jourst]
361 gros_dico["data_j"] = l_jours2
362 gros_dico["data_p"] = l_poids
363 gros_dico["data_jours_taille"] = l_jourst2
364 gros_dico["data_taille"] = l_taille
365 # gérer la date de naissance
366 if gros_dico.get("naissance","") != "":
367 gros_dico["naissance"] = convertit_date_vers_texte(gros_dico["naissance"])
368 # gérer l'age maxi
369 gros_dico["maxi"] = convertit_age_vers_texte(gros_dico["maxi"])
370 # gérer les couleurs
371 for clecouleur in DEFAUT["couleurs"]:
372 gros_dico["couleurs"][clecouleur] = tuple_vers_rgb(gros_dico["couleurs"][clecouleur])
373
374 # Enlever ce qui ne se sauvegarde pas si y'a
375 if "non_sauve" in gros_dico:
376 del gros_dico["non_sauve"]
377
378 return json.dumps(gros_dico, indent=2,ensure_ascii=False )
379
380 def fusionne_donnees(listes_jours,listes_donnees):
381 """ prend en argument deux dicos de listes. Chaque liste de jours est associée à une liste
382 de données (par la même clé de type de données). Il faut les fusionner pour avoir une liste de dictionnaires, de type
383 ("age":truc, "donnee1":truc, "donnee2":truc, ...) triée par ordre de jours. Si jamais une des données est vide,
384 le champ du dictionnaire n'est pas rempli"""
385
386 def fini(lj):
387 """ teste si les listes sont toutes vides """
388 for l in lj.values():
389 if l!=[]:
390 return False
391 return True
392
393 def mini(lj):
394 """ renvoie la clé de la liste où il y a le min """
395 cle_mini = CONFIG["liste_typedonnees"][0]
396 for (cle,liste) in lj.items():
397 if lj[cle_mini]== []:
398 cle_mini = cle
399 elif lj[cle] != []:
400 if convertit_jours_vers_python(lj[cle][0],initialise_erreurs())<convertit_jours_vers_python(lj[cle_mini][0],initialise_erreurs()):
401 cle_mini = cle
402 return cle_mini
403
404 liste_f = []
405 while not(fini(listes_jours)):
406 typedonnee = mini(listes_jours)
407 # On extrait les données dans les deux listes (jours et données)
408 jour = listes_jours[typedonnee].pop(0)
409 donnee = listes_donnees[typedonnee].pop(0)
410 if liste_f == [] or jour != liste_f[-1]["age"]: # Si le jour est un "nouveau" jour
411 liste_f.append({"age":jour})
412 # On met à jour l'élément
413 liste_f[-1][typedonnee] = donnee
414
415 return liste_f
416
417
418 ### COnversion json vers formulaire
419 # Json -> formulaire HTML
420 def fichier_json_vers_configdonnees(chaine,liste_err):
421 """ prend le json importé (chaine) et l'exporte vers les valeurs du formulaire
422 Renvoyé sous forme de dictionnaire (mais adapté au formulaire web)"""
423 debug("json vers config : Prêt à interpréter le json",liste_err)
424 try:
425 valform = json.loads(chaine)
426 except :
427 erreur("Impossible de lire le fichier json !",liste_err)
428 return {}
429 # Il faut maintenant récupérer les l_jours et l_poids puis les remettre
430 # sous forme de age_i et poids_i
431
432 listes_jours = {}
433 listes_donnees = {}
434 for typed in CONFIG["liste_typedonnees"]:
435 if typed == "poids": # pour la rétrocompatibilité
436 listes_jours[typed] = valform.get("data_j",[])
437 listes_donnees[typed] = valform.get("data_p",[])
438 else:
439 listes_jours[typed] = valform.get("data_jours_"+typed,[])
440 listes_donnees[typed] = valform.get("data_"+typed,[])
441
442 debug("Avant fusion : listes jours "+str(listes_jours),liste_err)
443 liste_donnees = fusionne_donnees(listes_jours,listes_donnees)
444 debug("Fusion de listes ok. Liste moche : "+str(liste_donnees),liste_err)
445 for i in range(len(liste_donnees)):
446 for (cle,val) in liste_donnees[i].items():
447 valform[cle+"_"+str(i)] = val
448
449 valform["nb_data"] = max(len(liste_donnees) +2,DEFAUT["nb_data"])
450
451 return valform
452
453
454