]>
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 chaine2
= chaine
.replace(",",".")
106 chaine2
= chaine2
.replace(" ","")
109 donnee
= float(chaine2
)
111 warning(typedonnee
+" impossible à lire : "+chaine
,liste_err
)
113 if not( 0<=donnee
<CONFIG
[typedonnee
+"_maxi"]):
114 warning(typedonnee
+"incohérent(e) : "+str(donnee
),liste_err
)
119 #########################
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("-")
128 warning("La date : "+chaine
+" est invalide !",liste_err
)
131 debug("Conversion de la date "+chaine
+". Découpage : "+str(liste
),liste_err
)
133 date
= datetime
.date(int(liste
[0]),int(liste
[1]),int(liste
[2]))
136 warning("Impossible de lire la date "+chaine
+". Format accepté : aaaa-mm-jj",liste_err
)
140 def convertit_date_vers_texte(date
):
141 """ convertit une date python en format texte aaaa-mm-jj"""
145 return (str(date
.year
)+"-"+str(date
.month
)+"-"+str(date
.day
))
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
154 warning("La différence entre les dates est négative... :/")
159 ################### On regroupe tout ce qui gère les données en une fonction
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"""
165 # Régler la configuration
166 config
= gere_configuration(data
,liste_err
)
168 # récupérer les données
171 for typed
in CONFIG
["liste_typedonnees"]:
172 listes_jours
[typed
],listes_donnees
[typed
] = gere_donnees(data
,config
["naissance"],typed
,liste_err
)
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"])
179 return (config
,listes_jours
,listes_donnees
)
183 ########### Fonctions qui gèretn les données web vers python
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"""
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."""
198 configuration
= {"non_sauve": {}
}
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"]]
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
)
209 configuration
["sexe"] = sexe
211 naissance
= data
.get("naissance","")
213 naissance
= convertit_date_vers_python(naissance
,liste_err
)
214 configuration
["naissance"] = naissance
216 prematurite
= data
.get("prematurite","")
217 j
= convertit_jours_vers_python(prematurite
,liste_err
)
218 configuration
["prematurite"] = convertit_age_vers_texte(j
)
220 configuration
["agecorrige"] = gere_checkbox(data
.get("agecorrige",""))
222 # Type de courbe. Au pire on met P
223 tyc
= data
.get("typecourbe","")
224 if not (tyc
in ["P","Z"]):
226 configuration
["typecourbe"] = tyc
229 unite
= data
.get("unite","")
230 if not (unite
in CONFIG
["liste_unites"]):
232 #warning("L'unité "+unite+" n'est pas reconnue !",liste_err)
233 configuration
["unite"] = unite
236 configuration
["grille"] = gere_checkbox(data
.get("grille",""))
238 # tracer ou non les courbes vides
239 configuration
["tracevide"] = gere_checkbox(data
.get("tracevide",""))
241 # Même échelle sur tous les graphiques
242 configuration
["memechelle"] = gere_checkbox(data
.get("memechelle",""))
245 # maxi. 0 signifie qu'on veut pas de maxi
246 maxi
= data
.get("maxi","")
248 configuration
["maxi"] = 0
250 configuration
["maxi"] = int(convertit_jours_vers_python(maxi
,liste_err
))
252 # dimensions du graphique
253 largeur
= data
.get("largeur","")
255 largeur
= DEFAUT
["largeur_graphique"]
258 largeur
= int(largeur
)
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
270 hauteur
= data
.get("hauteur","")
272 hauteur
= DEFAUT
["hauteur_graphique"]
275 hauteur
= int(hauteur
)
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
287 # existence et position de la légende
288 configuration
["legende"] = gere_checkbox(data
.get("legende",""))
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
295 configuration
["couleurs"] = {}
297 for clecouleur
in DEFAUT
["couleurs"]:
298 coul
= rgb_vers_tuple(data
.get("couleur_"+clecouleur
,""),CONFIG
["couleurs"][clecouleur
],liste_err
)
299 configuration
["couleurs"][clecouleur
] = coul
302 configuration
["non_sauve"]["grilleamelio"] = gere_checkbox(data
.get("grilleamelio",""))
305 #### La partie extrapolation n'a pas besoin d'être sauvée
306 configuration
["non_sauve"]["prolongercourbes"] = gere_checkbox(data
.get("prolongercourbes",""))
308 # Valeur par défaut : 1
309 debug(data
.get("nbextradata", "aaargh"), liste_err
)
310 nbextradata
= data
.get("nbextradata",1)
312 nbextradata
= int(nbextradata
)
314 warning("Le nombre de données sur lequel on extrapole est invalide : "+nbextradata
, liste_err
)
316 configuration
["non_sauve"]["nbextradata"] = nbextradata
318 if data
.get("calculextradata_type","") in CONFIG
["liste_typedonnees"]:
319 configuration
["non_sauve"]["calculextradata_type"] = data
.get("calculextradata_type","")
320 configuration
["non_sauve"]["calculextradata_age"] = convertit_jours_vers_python(data
.get("calculextradata_age","0j"),liste_err
)
322 configuration
["non_sauve"]["calculextradata_type"] = ""
323 # On ne met rien dans l'âge, pas la peine
325 ctyped
= data
.get("calculextratemps_type","")
326 if ctyped
in CONFIG
["liste_typedonnees"]:
327 configuration
["non_sauve"]["calculextratemps_type"] = ctyped
328 configuration
["non_sauve"]["calculextratemps_val"] = convertit_donnee_vers_python(data
.get("calculextratemps_val",""), ctyped
, liste_err
)
330 configuration
["non_sauve"]["calculextratemps_type"] = ""
336 ## web vers python : données
337 def gere_donnees(data
,naissance
,typedonnee
,liste_err
):
338 """ prend en argument le dictionnaire de requête, et la date de
339 naissance (éventuellement vide), et construit deux listes :
340 l_jours et l_data correspondantes.
341 Il faut donner en argument le type de données : voir
342 CONFIG["liste_typedonnees"]"""
343 if typedonnee
not in CONFIG
["liste_typedonnees"]:
344 warning("gere_donnees : le type de données : "+typedonnee
+" est invalide !! Types acceptés : "+str(CONFIG
["liste_typedonnees"]),liste_err
)
347 # On construit une liste de couples d'abord
351 # On va chercher si y'a des données à donnee_i
352 while typedonnee
+"_"+str(i
) in data
.keys():
353 if data
[typedonnee
+"_"+str(i
)] != "":
354 donnee
= convertit_donnee_vers_python(data
[typedonnee
+"_"+str(i
)],typedonnee
,liste_err
)
355 age
= data
.get("age_"+str(i
),"")
357 age
= convertit_jours_vers_python(age
,liste_err
)
358 liste_donnees
.append((age
,donnee
))
360 date
= data
.get("date_"+str(i
),"")
361 datep
= convertit_date_vers_python(date
,liste_err
)
364 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
)
365 elif datep
!= "": # la date est valide et on a une date de naissance
366 age
= delta_date(datep
,naissance
)
367 liste_donnees
.append((age
,donnee
))
371 liste_donnees
.sort(key
=lambda x
: x
[0])
374 l_jours
= [x
[0] for x
in liste_donnees
]
375 l_donnee
= [x
[1] for x
in liste_donnees
]
377 return (l_jours
,l_donnee
)
381 #### export vers json
383 def donnees_vers_json(l_jours
,l_poids
,l_jourst
,l_taille
,config
):
384 """ retourne le json à renvoyer"""
385 gros_dico
= copy
.deepcopy(config
)
386 l_jours2
= [convertit_age_vers_texte(d
) for d
in l_jours
]
387 l_jourst2
= [convertit_age_vers_texte(d
) for d
in l_jourst
]
388 gros_dico
["data_j"] = l_jours2
389 gros_dico
["data_p"] = l_poids
390 gros_dico
["data_jours_taille"] = l_jourst2
391 gros_dico
["data_taille"] = l_taille
392 # gérer la date de naissance
393 if gros_dico
.get("naissance","") != "":
394 gros_dico
["naissance"] = convertit_date_vers_texte(gros_dico
["naissance"])
396 gros_dico
["maxi"] = convertit_age_vers_texte(gros_dico
["maxi"])
398 for clecouleur
in DEFAUT
["couleurs"]:
399 gros_dico
["couleurs"][clecouleur
] = tuple_vers_rgb(gros_dico
["couleurs"][clecouleur
])
401 # Enlever ce qui ne se sauvegarde pas si y'a
402 if "non_sauve" in gros_dico
:
403 del gros_dico
["non_sauve"]
405 return json
.dumps(gros_dico
, indent
=2,ensure_ascii
=False )
407 def fusionne_donnees(listes_jours
,listes_donnees
):
408 """ prend en argument deux dicos de listes. Chaque liste de jours est associée à une liste
409 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
410 ("age":truc, "donnee1":truc, "donnee2":truc, ...) triée par ordre de jours. Si jamais une des données est vide,
411 le champ du dictionnaire n'est pas rempli"""
414 """ teste si les listes sont toutes vides """
415 for l
in lj
.values():
421 """ renvoie la clé de la liste où il y a le min """
422 cle_mini
= CONFIG
["liste_typedonnees"][0]
423 for (cle
,liste
) in lj
.items():
424 if lj
[cle_mini
]== []:
427 if convertit_jours_vers_python(lj
[cle
][0],initialise_erreurs())<convertit_jours_vers_python(lj
[cle_mini
][0],initialise_erreurs()):
432 while not(fini(listes_jours
)):
433 typedonnee
= mini(listes_jours
)
434 # On extrait les données dans les deux listes (jours et données)
435 jour
= listes_jours
[typedonnee
].pop(0)
436 donnee
= listes_donnees
[typedonnee
].pop(0)
437 if liste_f
== [] or jour
!= liste_f
[-1]["age"]: # Si le jour est un "nouveau" jour
438 liste_f
.append({"age":jour}
)
439 # On met à jour l'élément
440 liste_f
[-1][typedonnee
] = donnee
445 ### COnversion json vers formulaire
446 # Json -> formulaire HTML
447 def fichier_json_vers_configdonnees(chaine
,liste_err
):
448 """ prend le json importé (chaine) et l'exporte vers les valeurs du formulaire
449 Renvoyé sous forme de dictionnaire (mais adapté au formulaire web)"""
450 debug("json vers config : Prêt à interpréter le json",liste_err
)
452 valform
= json
.loads(chaine
)
454 erreur("Impossible de lire le fichier json !",liste_err
)
456 # Il faut maintenant récupérer les l_jours et l_poids puis les remettre
457 # sous forme de age_i et poids_i
461 for typed
in CONFIG
["liste_typedonnees"]:
462 if typed
== "poids": # pour la rétrocompatibilité
463 listes_jours
[typed
] = valform
.get("data_j",[])
464 listes_donnees
[typed
] = valform
.get("data_p",[])
466 listes_jours
[typed
] = valform
.get("data_jours_"+typed
,[])
467 listes_donnees
[typed
] = valform
.get("data_"+typed
,[])
469 debug("Avant fusion : listes jours "+str(listes_jours
),liste_err
)
470 liste_donnees
= fusionne_donnees(listes_jours
,listes_donnees
)
471 debug("Fusion de listes ok. Liste moche : "+str(liste_donnees
),liste_err
)
472 for i
in range(len(liste_donnees
)):
473 for (cle
,val
) in liste_donnees
[i
].items():
474 valform
[cle
+"_"+str(i
)] = val
476 valform
["nb_data"] = max(len(liste_donnees
) +2,DEFAUT
["nb_data"])