]>
git.immae.eu Git - perso/Denise/oms.git/blob - gestion_donnees.py
2 # -*- coding: utf-8 -*-
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
13 ### Les données "tournent" selon :
14 ### python -> json -> (export/import) -> formulaire HTML -> données POST -> python etc
16 ############ Fonctions de conversion
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
24 # debug("conversion de "+chaine+" vers un nb de jours",liste_err)
29 chainenombre
+= lettre
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"]
35 elif lettre
== 'm' or lettre
== 'M':
37 agejours
+= int(chainenombre
)*CONFIG
["jours_dans_mois"]
39 elif lettre
== 's' or lettre
== 'S':
41 agejours
+= int(chainenombre
)*CONFIG
["jours_dans_semaine"]
43 elif lettre
== 'j' or lettre
== 'J':
45 agejours
+= int(chainenombre
)
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
)
54 warning("L'âge est négatif !",liste_err
)
56 # debug("On a convertit ! Résultat : "+str(agejours),liste_err)
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"])
70 chaine
+= str(annees
)+"a"
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"
77 ##########################
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"""
83 return CONFIG
["jours_defaut_donneesvides"]
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
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"""
98 chaine2
= unidecode
.unidecode(chaine2
)
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 Si la chaine est en fait déjà au format float, on laisse tel quel"""
106 if type(chaine
) == float:
108 chaine2
= chaine
.replace(",",".")
109 chaine2
= chaine2
.replace(" ","")
113 donnee
= float(chaine2
)
115 warning(typedonnee
+" impossible à lire : "+chaine
,liste_err
)
118 # Pour le poids, un cas particulier
119 if typedonnee
== "poids" and donnee
> CONFIG
["poids_maxi_conversion"]:
120 donnee
= donnee
/1000 # conversion en grammes
121 if not( 0<=donnee
<CONFIG
[typedonnee
+"_maxi"]):
122 warning(typedonnee
+" incohérent(e) : "+str(donnee
),liste_err
)
127 #########################
130 def convertit_date_vers_python(chaine
,liste_err
):
131 """ prend une chaine comme renvoyée par un champ de formulaire date
132 aaaa-mm-jj et en fait une date python
133 renvoie "" si ne marche pas"""
134 liste
= chaine
.split("-")
136 warning("La date : "+chaine
+" est invalide !",liste_err
)
139 debug("Conversion de la date "+chaine
+". Découpage : "+str(liste
),liste_err
)
141 date
= datetime
.date(int(liste
[0]),int(liste
[1]),int(liste
[2]))
144 warning("Impossible de lire la date "+chaine
+". Format accepté : aaaa-mm-jj",liste_err
)
148 def convertit_date_vers_texte(date
):
149 """ convertit une date python en format texte aaaa-mm-jj"""
153 return (str(date
.year
)+"-"+str(date
.month
)+"-"+str(date
.day
))
156 def delta_date(date1
,datenaissance
):
157 """ renvoie le nombre de jours (entier) entre date1 et datenaissance format "datetime"
158 datenaissance est supposée antérieure. Erreur sinon."""
159 d
= date1
- datenaissance
162 warning("La différence entre les dates est négative... :/")
167 ################### On regroupe tout ce qui gère les données en une fonction
169 def web_vers_python(data
,liste_err
, court
=False):
170 """ prend en argument le dictionnaire de requête et renvoie la config, et les
172 court : si True est précisé, on ne met que le nom dans la config (enfant
175 # Régler la configuration
176 config
= gere_configuration(data
,liste_err
, court
)
178 # récupérer les données
181 for typed
in CONFIG
["liste_typedonnees"]:
182 listes_jours
[typed
],listes_donnees
[typed
] = gere_donnees(data
,config
["naissance"],typed
,liste_err
)
184 # Si on veut extrapoler au-delà du jour maxi, on adapte
186 # Si on a choisi la même échelle de données
187 if config
.get("memechelle") == "oui":
188 config
["non_sauve"]["maxi"] = calcule_max_graphique([j
for lj
in listes_jours
.values() for j
in lj
])
189 # En cas d'extrapolation, on prend le maxi
190 if config
["non_sauve"]["calculextradata_type"] !="" and config
["non_sauve"]["calculextradata_age"]>config
["non_sauve"]["maxi"]:
191 config
["non_sauve"]["maxi"] = int(config
["non_sauve"]["calculextradata_age"]) +1
192 config
["non_sauve"]["unite"] = choix_unite(config
["non_sauve"]["maxi"])
194 return (config
,listes_jours
,listes_donnees
)
198 ########### Fonctions qui gèretn les données web vers python
200 def gere_checkbox(chaine
):
201 """ prend en arg une chaine, et renvoie "oui" si c'est "on" (sortie de la checkbox)
202 et chaîne vide si n'importe quoi d'autre"""
208 def gere_symbole(chaine
):
209 """ prend en arg une chaîne genre "o", ">" et vérifie si c'est un symbole valide.
210 Renvoie ce symbole-là ou le défaut"""
211 if chaine
in CONFIG
["liste_symboles"]:
214 return DEFAUT
["symbole"]
216 def gere_configuration(data
,liste_err
, court
=False):
217 """ prend en argument le dictionnaire de requête (configuration imparfaite), et
218 construit le dictionnaire de configuration qui va bien.
219 Vérifie que chaque entrée est cohérente évidemment.
220 court : si mis à True, on ne met que le nom dans la configuraion,
221 ainsi que la date de naissance et le sexe"""
223 configuration
= {"non_sauve": {}
}
225 # Pour le nom, osef qu'il soit vide
226 nom
= data
.get("nom","")
227 # Par contre s'il est trop long on le tronque
228 configuration
["nom"] = nom
[:CONFIG
["longueur_max_nom_bebe"]]
230 naissance
= data
.get("naissance","")
232 naissance
= convertit_date_vers_python(naissance
,liste_err
)
233 configuration
["naissance"] = naissance
235 sexe
= data
.get("sexe","")
236 if not (sexe
in ["F","M","N"]):
237 warning("Le sexe de l'enfant est invalide ! "+sexe
,liste_err
)
239 configuration
["sexe"] = sexe
243 prematurite
= data
.get("prematurite","")
244 j
= convertit_jours_vers_python(prematurite
,liste_err
)
245 configuration
["prematurite"] = convertit_age_vers_texte(j
)
247 configuration
["agecorrige"] = gere_checkbox(data
.get("agecorrige",""))
249 # Type de courbe. Au pire on met P
250 tyc
= data
.get("typecourbe","")
251 if not (tyc
in ["P","Z"]):
253 configuration
["typecourbe"] = tyc
256 unite
= data
.get("unite","")
257 if not (unite
in CONFIG
["liste_unites"]):
259 #warning("L'unité "+unite+" n'est pas reconnue !",liste_err)
260 configuration
["unite"] = unite
263 configuration
["grille"] = gere_checkbox(data
.get("grille",""))
265 # tracer ou non les courbes vides
266 configuration
["tracevide"] = gere_checkbox(data
.get("tracevide",""))
268 # Même échelle sur tous les graphiques
269 configuration
["memechelle"] = gere_checkbox(data
.get("memechelle",""))
272 # maxi. 0 signifie qu'on veut pas de maxi
273 maxi
= data
.get("maxi","")
275 configuration
["maxi"] = 0
277 configuration
["maxi"] = int(convertit_jours_vers_python(maxi
,liste_err
))
279 # dimensions du graphique
280 largeur
= data
.get("largeur","")
282 largeur
= DEFAUT
["largeur_graphique"]
285 largeur
= int(largeur
)
287 warning("La largeur "+largeur
+"est invalide !",liste_err
)
288 largeur
= DEFAUT
["largeur_graphique"]
289 if largeur
> CONFIG
["largeur_graphique_max"]:
290 largeur
= CONFIG
["largeur_graphique_max"]
291 warning("Largeur du graphique trop grande !",liste_err
)
292 elif largeur
< CONFIG
["largeur_graphique_min"]:
293 largeur
= CONFIG
["largeur_graphique_min"]
294 warning("Largeur du graphique trop petite !",liste_err
)
295 configuration
["largeur"] = largeur
297 hauteur
= data
.get("hauteur","")
299 hauteur
= DEFAUT
["hauteur_graphique"]
302 hauteur
= int(hauteur
)
304 warning("La hauteur "+hauteur
+"est invalide !",liste_err
)
305 hauteur
= DEFAUT
["hauteur_graphique"]
306 if hauteur
> CONFIG
["hauteur_graphique_max"]:
307 hauteur
= CONFIG
["hauteur_graphique_max"]
308 warning("Hauteur du graphique trop grande !",liste_err
)
309 elif hauteur
< CONFIG
["hauteur_graphique_min"]:
310 hauteur
= CONFIG
["hauteur_graphique_min"]
311 warning("Hauteur du graphique trop petite !",liste_err
)
312 configuration
["hauteur"] = hauteur
314 # existence et position de la légende
315 configuration
["legende"] = gere_checkbox(data
.get("legende",""))
317 positionlegende
= data
.get("positionlegende","")
318 if not(positionlegende
in ['upper left','upper right','lower left','lower right']):
319 positionlegende
= "upper left"
320 configuration
["positionlegende"] = positionlegende
322 configuration
["couleurs"] = {}
324 for clecouleur
in DEFAUT
["couleurs"]:
325 coul
= rgb_vers_tuple(data
.get("couleur_"+clecouleur
,""),CONFIG
["couleurs"][clecouleur
],liste_err
)
326 configuration
["couleurs"][clecouleur
] = coul
329 configuration
["symbole"] = gere_symbole( data
.get("symbole", ""))
331 configuration
["non_sauve"]["grilleamelio"] = gere_checkbox(data
.get("grilleamelio",""))
334 #### La partie extrapolation n'a pas besoin d'être sauvée
335 configuration
["non_sauve"]["prolongercourbes"] = gere_checkbox(data
.get("prolongercourbes",""))
337 # Valeur par défaut : 1
338 debug(data
.get("nbextradata", "aaargh"), liste_err
)
339 nbextradata
= data
.get("nbextradata",1)
341 nbextradata
= int(nbextradata
)
343 warning("Le nombre de données sur lequel on extrapole est invalide : "+nbextradata
, liste_err
)
345 configuration
["non_sauve"]["nbextradata"] = nbextradata
347 if data
.get("calculextradata_type","") in CONFIG
["liste_typedonnees"]:
348 configuration
["non_sauve"]["calculextradata_type"] = data
.get("calculextradata_type","")
349 configuration
["non_sauve"]["calculextradata_age"] = convertit_jours_vers_python(data
.get("calculextradata_age","0j"),liste_err
)
351 configuration
["non_sauve"]["calculextradata_type"] = ""
352 # On ne met rien dans l'âge, pas la peine
354 ctyped
= data
.get("calculextratemps_type","")
355 if ctyped
in CONFIG
["liste_typedonnees"]:
356 configuration
["non_sauve"]["calculextratemps_type"] = ctyped
357 configuration
["non_sauve"]["calculextratemps_val"] = convertit_donnee_vers_python(data
.get("calculextratemps_val",""), ctyped
, liste_err
)
359 configuration
["non_sauve"]["calculextratemps_type"] = ""
361 # Tracer les calculs sur la grille
362 configuration
["non_sauve"]["calculextradata_trace"] = gere_checkbox(data
.get("calculextradata_trace"))
363 configuration
["non_sauve"]["calculextratemps_trace"] = gere_checkbox(data
.get("calculextratemps_trace"))
370 ## web vers python : données
371 def gere_donnees(data
,naissance
,typedonnee
,liste_err
):
372 """ prend en argument le dictionnaire de requête, et la date de
373 naissance (éventuellement vide), et construit deux listes :
374 l_jours et l_data correspondantes.
375 Il faut donner en argument le type de données : voir
376 CONFIG["liste_typedonnees"]"""
377 if typedonnee
not in CONFIG
["liste_typedonnees"]:
378 warning("gere_donnees : le type de données : "+typedonnee
+" est invalide !! Types acceptés : "+str(CONFIG
["liste_typedonnees"]),liste_err
)
381 # On construit une liste de couples d'abord
385 # On va chercher si y'a des données à donnee_i
386 while typedonnee
+"_"+str(i
) in data
.keys():
387 if data
[typedonnee
+"_"+str(i
)] != "":
388 donnee
= convertit_donnee_vers_python(data
[typedonnee
+"_"+str(i
)],typedonnee
,liste_err
)
389 age
= data
.get("age_"+str(i
),"")
391 age
= convertit_jours_vers_python(age
,liste_err
)
392 liste_donnees
.append((age
,donnee
))
394 date
= data
.get("date_"+str(i
),"")
395 datep
= convertit_date_vers_python(date
,liste_err
)
398 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
)
399 elif datep
!= "": # la date est valide et on a une date de naissance
400 age
= delta_date(datep
,naissance
)
401 liste_donnees
.append((age
,donnee
))
405 liste_donnees
.sort(key
=lambda x
: x
[0])
408 l_jours
= [x
[0] for x
in liste_donnees
]
409 l_donnee
= [x
[1] for x
in liste_donnees
]
411 return (l_jours
,l_donnee
)
415 #### export vers json
417 def donnees_vers_json(l_jours
,l_poids
,l_jourst
,l_taille
,config
):
418 """ retourne le json à renvoyer"""
419 gros_dico
= copy
.deepcopy(config
)
420 l_jours2
= [convertit_age_vers_texte(d
) for d
in l_jours
]
421 l_jourst2
= [convertit_age_vers_texte(d
) for d
in l_jourst
]
422 gros_dico
["data_j"] = l_jours2
423 gros_dico
["data_p"] = l_poids
424 gros_dico
["data_jours_taille"] = l_jourst2
425 gros_dico
["data_taille"] = l_taille
426 # gérer la date de naissance
427 if gros_dico
.get("naissance","") != "":
428 gros_dico
["naissance"] = convertit_date_vers_texte(gros_dico
["naissance"])
430 gros_dico
["maxi"] = convertit_age_vers_texte(gros_dico
["maxi"])
432 for clecouleur
in DEFAUT
["couleurs"]:
433 gros_dico
["couleurs"][clecouleur
] = tuple_vers_rgb(gros_dico
["couleurs"][clecouleur
])
435 # Enlever ce qui ne se sauvegarde pas si y'a
436 if "non_sauve" in gros_dico
:
437 del gros_dico
["non_sauve"]
439 return json
.dumps(gros_dico
, indent
=2,ensure_ascii
=False )
441 def fusionne_donnees(listes_jours
,listes_donnees
):
442 """ prend en argument deux dicos de listes. Chaque liste de jours est associée à une liste
443 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
444 ("age":truc, "donnee1":truc, "donnee2":truc, ...) triée par ordre de jours. Si jamais une des données est vide,
445 le champ du dictionnaire n'est pas rempli"""
448 """ teste si les listes sont toutes vides """
449 for l
in lj
.values():
455 """ renvoie la clé de la liste où il y a le min """
456 cle_mini
= CONFIG
["liste_typedonnees"][0]
457 for (cle
,liste
) in lj
.items():
458 if lj
[cle_mini
]== []:
461 if convertit_jours_vers_python(lj
[cle
][0],initialise_erreurs())<convertit_jours_vers_python(lj
[cle_mini
][0],initialise_erreurs()):
466 while not(fini(listes_jours
)):
467 typedonnee
= mini(listes_jours
)
468 # On extrait les données dans les deux listes (jours et données)
469 jour
= listes_jours
[typedonnee
].pop(0)
470 donnee
= listes_donnees
[typedonnee
].pop(0)
471 if liste_f
== [] or jour
!= liste_f
[-1]["age"]: # Si le jour est un "nouveau" jour
472 liste_f
.append({"age":jour}
)
473 # On met à jour l'élément
474 liste_f
[-1][typedonnee
] = donnee
479 ### COnversion json vers formulaire
480 # Json -> formulaire HTML
481 def fichier_json_vers_configdonnees(chaine
,liste_err
):
482 """ prend le json importé (chaine) et l'exporte vers les valeurs du formulaire
483 Renvoyé sous forme de dictionnaire (mais adapté au formulaire web)"""
484 debug("json vers config : Prêt à interpréter le json",liste_err
)
486 valform
= json
.loads(chaine
)
488 erreur("Impossible de lire le fichier json !",liste_err
)
490 # Il faut maintenant récupérer les l_jours et l_poids puis les remettre
491 # sous forme de age_i et poids_i
495 for typed
in CONFIG
["liste_typedonnees"]:
496 if typed
== "poids": # pour la rétrocompatibilité
497 listes_jours
[typed
] = valform
.get("data_j",[])
498 listes_donnees
[typed
] = valform
.get("data_p",[])
500 listes_jours
[typed
] = valform
.get("data_jours_"+typed
,[])
501 listes_donnees
[typed
] = valform
.get("data_"+typed
,[])
503 debug("Avant fusion : listes jours "+str(listes_jours
),liste_err
)
504 liste_donnees
= fusionne_donnees(listes_jours
,listes_donnees
)
505 debug("Fusion de listes ok. Liste moche : "+str(liste_donnees
),liste_err
)
506 for i
in range(len(liste_donnees
)):
507 for (cle
,val
) in liste_donnees
[i
].items():
508 valform
[cle
+"_"+str(i
)] = val
510 valform
["nb_data"] = max(len(liste_donnees
) +2,DEFAUT
["nb_data"])
516 #### Pour l'insertion d'une 2e (ou plus) courbe sur le graphique, ue fonction qui sépare tout ça
517 def eclate_donnees_additionnelles(conf
, ljours
, ldonnees
, symb
):
518 """ conf est la config (on ne garde que le nom) pour un enfant additionnel,
519 ljours et ldonnees les dictionnaires de listes contenant les données.
520 symb est le symbole choisi pour cette courbe additionnelle (déjà vérifié)
521 On fabrique un joli dictionnaire typed -> (conf lj, ldonnee) avec le nom de l'enfant,
522 et les données pour chaque typed"""
525 conf
["symbole"] = symb
# On ajoute le symbole additionnel
526 for typed
in CONFIG
["liste_typedonnees"]:
527 retour
[typed
] = (conf
, ljours
[typed
], ldonnees
[typed
])