]> git.immae.eu Git - perso/Denise/oms.git/blame - 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
CommitLineData
5679dfd0
DL
1#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3
d03279e7 4from configuration import CONFIG,DEFAUT
a680b2f7
DL
5from gestion_erreurs import debug, warning, erreur, initialise_erreurs
6from gestion_couleurs import rgb_vers_tuple, tuple_vers_rgb
7from gestion_unites import choix_unite
5679dfd0 8import datetime
be2bf515
DL
9import json
10import unidecode
915e90bb 11import copy
5679dfd0 12
61020126
DL
13### Les données "tournent" selon :
14### python -> json -> (export/import) -> formulaire HTML -> données POST -> python etc
5679dfd0
DL
15
16############ Fonctions de conversion
17
18def convertit_jours_vers_python(chaine,liste_err):
19 """ convertit une chaine de type 1a 3m 1s 10j en jours
be2bf515 20 Renvoie un nombre de jours en float
5679dfd0
DL
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 """
b5ac625b 24# debug("conversion de "+chaine+" vers un nb de jours",liste_err)
5679dfd0
DL
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à
d03279e7 33 agejours += int(chainenombre)*CONFIG["jours_dans_annee"]
5679dfd0
DL
34 chainenombre = ""
35 elif lettre == 'm' or lettre == 'M':
36 # On a trouvé le mois
d03279e7 37 agejours += int(chainenombre)*CONFIG["jours_dans_mois"]
5679dfd0
DL
38 chainenombre = ""
39 elif lettre == 's' or lettre == 'S':
40 # la semaine
d03279e7 41 agejours += int(chainenombre)*CONFIG["jours_dans_semaine"]
5679dfd0
DL
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 != "":
be2bf515 52 agejours += int(chainenombre)
5679dfd0
DL
53 if agejours<0:
54 warning("L'âge est négatif !",liste_err)
55 agejours = 0
b5ac625b 56# debug("On a convertit ! Résultat : "+str(agejours),liste_err)
be2bf515 57 return agejours
5679dfd0 58
8b5845ff 59# python -> json
5679dfd0
DL
60def 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"""
d03279e7
DL
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"])
5679dfd0
DL
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
a680b2f7 76
5679dfd0 77##########################
be2bf515 78
8b5845ff
DL
79# fonction qui calcule "auto" le maxi du graphique en fonction du max
80def 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
c2fe511b 86 jour_maxi = int(jour_maxi* 1.2)+3 # on rajoute un peu
8b5845ff
DL
87 return jour_maxi
88
be2bf515
DL
89
90def 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]
5679dfd0 100
8b5845ff
DL
101def 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."""
5679dfd0
DL
105 chaine2 = chaine.replace(",",".")
106 chaine2 = chaine2.replace(" ","")
107
108 try:
8b5845ff 109 donnee = float(chaine2)
5679dfd0 110 except:
8b5845ff
DL
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
5679dfd0 117
5679dfd0
DL
118
119#########################
120
8b5845ff 121# web -> python
5679dfd0
DL
122def 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:
30158504 131 debug("Conversion de la date "+chaine+". Découpage : "+str(liste),liste_err)
9cb3c31c
DL
132 try:
133 date = datetime.date(int(liste[0]),int(liste[1]),int(liste[2]))
134 except:
135 date = ""
30158504 136 warning("Impossible de lire la date "+chaine+". Format accepté : aaaa-mm-jj",liste_err)
9cb3c31c 137 return date
8b5845ff
DL
138
139# python -> json
5679dfd0
DL
140def 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
148def 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:
a680b2f7 154 warning("La différence entre les dates est négative... :/")
5679dfd0
DL
155 return -1
156 return jours
157
158
8b5845ff
DL
159################### On regroupe tout ce qui gère les données en une fonction
160
161def 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
a680b2f7 183########### Fonctions qui gèretn les données web vers python
5679dfd0 184
8b5845ff
DL
185def 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 ""
5679dfd0
DL
192
193def gere_configuration(data,liste_err):
be2bf515 194 """ prend en argument le dictionnaire de requête (configuration imparfaite), et
5679dfd0
DL
195 construit le dictionnaire de configuration qui va bien.
196 Vérifie que chaque entrée est cohérente évidemment."""
a680b2f7 197 # Initialisation
685a5f75 198 configuration = {"non_sauve": {}}
5679dfd0
DL
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
d03279e7 203 configuration["nom"] = nom[:CONFIG["longueur_max_nom_bebe"]]
5679dfd0
DL
204
205 sexe = data.get("sexe","")
a46e1269 206 if not (sexe in ["F","M","N"]):
5679dfd0 207 warning("Le sexe de l'enfant est invalide ! "+sexe,liste_err)
a46e1269 208 sexe = "N"
5679dfd0
DL
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
b5ac625b
DL
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
5679dfd0
DL
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","")
9e4c51c7 230 if not (unite in CONFIG["liste_unites"]):
5679dfd0
DL
231 unite = ""
232 #warning("L'unité "+unite+" n'est pas reconnue !",liste_err)
233 configuration["unite"] = unite
9e4c51c7 234
5679dfd0 235 # grille
8b5845ff
DL
236 configuration["grille"] = gere_checkbox(data.get("grille",""))
237
238 # tracer ou non les courbes vides
239 configuration["tracevide"] = gere_checkbox(data.get("tracevide",""))
5679dfd0 240
8b5845ff
DL
241 # Même échelle sur tous les graphiques
242 configuration["memechelle"] = gere_checkbox(data.get("memechelle",""))
c2fe511b 243
8b5845ff 244
5679dfd0
DL
245 # maxi. 0 signifie qu'on veut pas de maxi
246 maxi = data.get("maxi","")
247 if maxi == "":
248 configuration["maxi"] = 0
249 else:
54c71831 250 configuration["maxi"] = int(convertit_jours_vers_python(maxi,liste_err))
5679dfd0
DL
251
252 # dimensions du graphique
be2bf515 253 largeur = data.get("largeur","")
5679dfd0 254 if largeur == "":
915e90bb 255 largeur = DEFAUT["largeur_graphique"]
5679dfd0
DL
256 else:
257 try:
258 largeur = int(largeur)
259 except:
260 warning("La largeur "+largeur+"est invalide !",liste_err)
915e90bb 261 largeur = DEFAUT["largeur_graphique"]
d03279e7
DL
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)
5679dfd0
DL
268 configuration["largeur"] = largeur
269
be2bf515 270 hauteur = data.get("hauteur","")
5679dfd0 271 if hauteur == "":
915e90bb 272 hauteur = DEFAUT["hauteur_graphique"]
5679dfd0
DL
273 else:
274 try:
275 hauteur = int(hauteur)
276 except:
277 warning("La hauteur "+hauteur+"est invalide !",liste_err)
915e90bb 278 hauteur = DEFAUT["hauteur_graphique"]
d03279e7
DL
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)
5679dfd0
DL
285 configuration["hauteur"] = hauteur
286
287 # existence et position de la légende
8b5845ff 288 configuration["legende"] = gere_checkbox(data.get("legende",""))
5679dfd0
DL
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
61020126 294
c2fe511b
DL
295 configuration["prolongercourbes"] = gere_checkbox(data.get("prolongercourbes",""))
296
a680b2f7 297 # Ceci n'a pas besoin d'être sauvé
685a5f75 298 configuration["non_sauve"]["grilleamelio"] = gere_checkbox(data.get("grilleamelio",""))
fd69b6b5 299
915e90bb
DL
300 configuration["couleurs"] = {}
301 # gérer les couleurs
915e90bb
DL
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
685a5f75 305
fd69b6b5 306
5679dfd0 307 return configuration
5679dfd0 308
a680b2f7 309## web vers python : données
8b5845ff
DL
310def 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
5679dfd0
DL
321 liste_donnees = []
322
323 i = 0
8b5845ff
DL
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)
5679dfd0
DL
328 age = data.get("age_"+str(i),"")
329 if age !="":
330 age = convertit_jours_vers_python(age,liste_err)
8b5845ff 331 liste_donnees.append((age,donnee))
5679dfd0
DL
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)
8b5845ff 340 liste_donnees.append((age,donnee))
5679dfd0
DL
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]
8b5845ff 348 l_donnee = [x[1] for x in liste_donnees]
5679dfd0 349
8b5845ff 350 return (l_jours,l_donnee)
be2bf515
DL
351
352
a680b2f7 353# python vers Json
8b5845ff 354#### export vers json
be2bf515 355
8b5845ff 356def donnees_vers_json(l_jours,l_poids,l_jourst,l_taille,config):
be2bf515 357 """ retourne le json à renvoyer"""
915e90bb 358 gros_dico = copy.deepcopy(config)
be2bf515 359 l_jours2 = [convertit_age_vers_texte(d) for d in l_jours]
8b5845ff 360 l_jourst2 = [convertit_age_vers_texte(d) for d in l_jourst]
be2bf515
DL
361 gros_dico["data_j"] = l_jours2
362 gros_dico["data_p"] = l_poids
8b5845ff
DL
363 gros_dico["data_jours_taille"] = l_jourst2
364 gros_dico["data_taille"] = l_taille
be2bf515
DL
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"])
fd69b6b5 370 # gérer les couleurs
915e90bb
DL
371 for clecouleur in DEFAUT["couleurs"]:
372 gros_dico["couleurs"][clecouleur] = tuple_vers_rgb(gros_dico["couleurs"][clecouleur])
fd69b6b5 373
8b5845ff
DL
374 # Enlever ce qui ne se sauvegarde pas si y'a
375 if "non_sauve" in gros_dico:
376 del gros_dico["non_sauve"]
5679dfd0 377
be2bf515 378 return json.dumps(gros_dico, indent=2,ensure_ascii=False )
5679dfd0 379
8b5845ff
DL
380def 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"""
a680b2f7 385
8b5845ff
DL
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] != []:
b5ac625b 400 if convertit_jours_vers_python(lj[cle][0],initialise_erreurs())<convertit_jours_vers_python(lj[cle_mini][0],initialise_erreurs()):
8b5845ff
DL
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
5679dfd0 417
a680b2f7 418### COnversion json vers formulaire
8b5845ff 419# Json -> formulaire HTML
d03279e7 420def fichier_json_vers_configdonnees(chaine,liste_err):
a680b2f7
DL
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)"""
d03279e7
DL
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 {}
be2bf515
DL
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
8b5845ff
DL
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
be2bf515 448
8b5845ff 449 valform["nb_data"] = max(len(liste_donnees) +2,DEFAUT["nb_data"])
5679dfd0 450
be2bf515 451 return valform
5679dfd0 452
5679dfd0 453
be2bf515 454