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