]>
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
)
114 # Pour le poids, un cas particulier
115 if typedonnee
== "poids" and donnee
> CONFIG
["poids_maxi_conversion"]:
116 donnee
= donnee
/1000 # conversion en grammes
117 if not( 0<=donnee
<CONFIG
[typedonnee
+"_maxi"]):
118 warning(typedonnee
+" incohérent(e) : "+str(donnee
),liste_err
)
123 #########################
126 def convertit_date_vers_python(chaine
,liste_err
):
127 """ prend une chaine comme renvoyée par un champ de formulaire date
128 aaaa-mm-jj et en fait une date python
129 renvoie "" si ne marche pas"""
130 liste
= chaine
.split("-")
132 warning("La date : "+chaine
+" est invalide !",liste_err
)
135 debug("Conversion de la date "+chaine
+". Découpage : "+str(liste
),liste_err
)
137 date
= datetime
.date(int(liste
[0]),int(liste
[1]),int(liste
[2]))
140 warning("Impossible de lire la date "+chaine
+". Format accepté : aaaa-mm-jj",liste_err
)
144 def convertit_date_vers_texte(date
):
145 """ convertit une date python en format texte aaaa-mm-jj"""
149 return (str(date
.year
)+"-"+str(date
.month
)+"-"+str(date
.day
))
152 def delta_date(date1
,datenaissance
):
153 """ renvoie le nombre de jours (entier) entre date1 et datenaissance format "datetime"
154 datenaissance est supposée antérieure. Erreur sinon."""
155 d
= date1
- datenaissance
158 warning("La différence entre les dates est négative... :/")
163 ################### On regroupe tout ce qui gère les données en une fonction
165 def web_vers_python(data
,liste_err
):
166 """ prend en argument le dictionnaire de requête et renvoie la config, et les
167 tableaux de donnée"""
169 # Régler la configuration
170 config
= gere_configuration(data
,liste_err
)
172 # récupérer les données
175 for typed
in CONFIG
["liste_typedonnees"]:
176 listes_jours
[typed
],listes_donnees
[typed
] = gere_donnees(data
,config
["naissance"],typed
,liste_err
)
178 # Si on a choisi la même échelle de données
179 if config
["memechelle"] == "oui":
180 config
["non_sauve"]["maxi"] = calcule_max_graphique([j
for lj
in listes_jours
.values() for j
in lj
])
181 config
["non_sauve"]["unite"] = choix_unite(config
["non_sauve"]["maxi"])
183 return (config
,listes_jours
,listes_donnees
)
187 ########### Fonctions qui gèretn les données web vers python
189 def gere_checkbox(chaine
):
190 """ prend en arg une chaine, et renvoie "oui" si c'est "on" (sortie de la checkbox)
191 et chaîne vide si n'importe quoi d'autre"""
197 def gere_configuration(data
,liste_err
):
198 """ prend en argument le dictionnaire de requête (configuration imparfaite), et
199 construit le dictionnaire de configuration qui va bien.
200 Vérifie que chaque entrée est cohérente évidemment."""
202 configuration
= {"non_sauve": {}
}
204 # Pour le nom, osef qu'il soit vide
205 nom
= data
.get("nom","")
206 # Par contre s'il est trop long on le tronque
207 configuration
["nom"] = nom
[:CONFIG
["longueur_max_nom_bebe"]]
209 sexe
= data
.get("sexe","")
210 if not (sexe
in ["F","M","N"]):
211 warning("Le sexe de l'enfant est invalide ! "+sexe
,liste_err
)
213 configuration
["sexe"] = sexe
215 naissance
= data
.get("naissance","")
217 naissance
= convertit_date_vers_python(naissance
,liste_err
)
218 configuration
["naissance"] = naissance
220 prematurite
= data
.get("prematurite","")
221 j
= convertit_jours_vers_python(prematurite
,liste_err
)
222 configuration
["prematurite"] = convertit_age_vers_texte(j
)
224 configuration
["agecorrige"] = gere_checkbox(data
.get("agecorrige",""))
226 # Type de courbe. Au pire on met P
227 tyc
= data
.get("typecourbe","")
228 if not (tyc
in ["P","Z"]):
230 configuration
["typecourbe"] = tyc
233 unite
= data
.get("unite","")
234 if not (unite
in CONFIG
["liste_unites"]):
236 #warning("L'unité "+unite+" n'est pas reconnue !",liste_err)
237 configuration
["unite"] = unite
240 configuration
["grille"] = gere_checkbox(data
.get("grille",""))
242 # tracer ou non les courbes vides
243 configuration
["tracevide"] = gere_checkbox(data
.get("tracevide",""))
245 # Même échelle sur tous les graphiques
246 configuration
["memechelle"] = gere_checkbox(data
.get("memechelle",""))
249 # maxi. 0 signifie qu'on veut pas de maxi
250 maxi
= data
.get("maxi","")
252 configuration
["maxi"] = 0
254 configuration
["maxi"] = int(convertit_jours_vers_python(maxi
,liste_err
))
256 # dimensions du graphique
257 largeur
= data
.get("largeur","")
259 largeur
= DEFAUT
["largeur_graphique"]
262 largeur
= int(largeur
)
264 warning("La largeur "+largeur
+"est invalide !",liste_err
)
265 largeur
= DEFAUT
["largeur_graphique"]
266 if largeur
> CONFIG
["largeur_graphique_max"]:
267 largeur
= CONFIG
["largeur_graphique_max"]
268 warning("Largeur du graphique trop grande !",liste_err
)
269 elif largeur
< CONFIG
["largeur_graphique_min"]:
270 largeur
= CONFIG
["largeur_graphique_min"]
271 warning("Largeur du graphique trop petite !",liste_err
)
272 configuration
["largeur"] = largeur
274 hauteur
= data
.get("hauteur","")
276 hauteur
= DEFAUT
["hauteur_graphique"]
279 hauteur
= int(hauteur
)
281 warning("La hauteur "+hauteur
+"est invalide !",liste_err
)
282 hauteur
= DEFAUT
["hauteur_graphique"]
283 if hauteur
> CONFIG
["hauteur_graphique_max"]:
284 hauteur
= CONFIG
["hauteur_graphique_max"]
285 warning("Hauteur du graphique trop grande !",liste_err
)
286 elif hauteur
< CONFIG
["hauteur_graphique_min"]:
287 hauteur
= CONFIG
["hauteur_graphique_min"]
288 warning("Hauteur du graphique trop petite !",liste_err
)
289 configuration
["hauteur"] = hauteur
291 # existence et position de la légende
292 configuration
["legende"] = gere_checkbox(data
.get("legende",""))
294 positionlegende
= data
.get("positionlegende","")
295 if not(positionlegende
in ['upper left','upper right','lower left','lower right']):
296 positionlegende
= "upper left"
297 configuration
["positionlegende"] = positionlegende
299 configuration
["couleurs"] = {}
301 for clecouleur
in DEFAUT
["couleurs"]:
302 coul
= rgb_vers_tuple(data
.get("couleur_"+clecouleur
,""),CONFIG
["couleurs"][clecouleur
],liste_err
)
303 configuration
["couleurs"][clecouleur
] = coul
306 configuration
["non_sauve"]["grilleamelio"] = gere_checkbox(data
.get("grilleamelio",""))
309 #### La partie extrapolation n'a pas besoin d'être sauvée
310 configuration
["non_sauve"]["prolongercourbes"] = gere_checkbox(data
.get("prolongercourbes",""))
312 # Valeur par défaut : 1
313 debug(data
.get("nbextradata", "aaargh"), liste_err
)
314 nbextradata
= data
.get("nbextradata",1)
316 nbextradata
= int(nbextradata
)
318 warning("Le nombre de données sur lequel on extrapole est invalide : "+nbextradata
, liste_err
)
320 configuration
["non_sauve"]["nbextradata"] = nbextradata
322 if data
.get("calculextradata_type","") in CONFIG
["liste_typedonnees"]:
323 configuration
["non_sauve"]["calculextradata_type"] = data
.get("calculextradata_type","")
324 configuration
["non_sauve"]["calculextradata_age"] = convertit_jours_vers_python(data
.get("calculextradata_age","0j"),liste_err
)
326 configuration
["non_sauve"]["calculextradata_type"] = ""
327 # On ne met rien dans l'âge, pas la peine
329 ctyped
= data
.get("calculextratemps_type","")
330 if ctyped
in CONFIG
["liste_typedonnees"]:
331 configuration
["non_sauve"]["calculextratemps_type"] = ctyped
332 configuration
["non_sauve"]["calculextratemps_val"] = convertit_donnee_vers_python(data
.get("calculextratemps_val",""), ctyped
, liste_err
)
334 configuration
["non_sauve"]["calculextratemps_type"] = ""
340 ## web vers python : données
341 def gere_donnees(data
,naissance
,typedonnee
,liste_err
):
342 """ prend en argument le dictionnaire de requête, et la date de
343 naissance (éventuellement vide), et construit deux listes :
344 l_jours et l_data correspondantes.
345 Il faut donner en argument le type de données : voir
346 CONFIG["liste_typedonnees"]"""
347 if typedonnee
not in CONFIG
["liste_typedonnees"]:
348 warning("gere_donnees : le type de données : "+typedonnee
+" est invalide !! Types acceptés : "+str(CONFIG
["liste_typedonnees"]),liste_err
)
351 # On construit une liste de couples d'abord
355 # On va chercher si y'a des données à donnee_i
356 while typedonnee
+"_"+str(i
) in data
.keys():
357 if data
[typedonnee
+"_"+str(i
)] != "":
358 donnee
= convertit_donnee_vers_python(data
[typedonnee
+"_"+str(i
)],typedonnee
,liste_err
)
359 age
= data
.get("age_"+str(i
),"")
361 age
= convertit_jours_vers_python(age
,liste_err
)
362 liste_donnees
.append((age
,donnee
))
364 date
= data
.get("date_"+str(i
),"")
365 datep
= convertit_date_vers_python(date
,liste_err
)
368 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
)
369 elif datep
!= "": # la date est valide et on a une date de naissance
370 age
= delta_date(datep
,naissance
)
371 liste_donnees
.append((age
,donnee
))
375 liste_donnees
.sort(key
=lambda x
: x
[0])
378 l_jours
= [x
[0] for x
in liste_donnees
]
379 l_donnee
= [x
[1] for x
in liste_donnees
]
381 return (l_jours
,l_donnee
)
385 #### export vers json
387 def donnees_vers_json(l_jours
,l_poids
,l_jourst
,l_taille
,config
):
388 """ retourne le json à renvoyer"""
389 gros_dico
= copy
.deepcopy(config
)
390 l_jours2
= [convertit_age_vers_texte(d
) for d
in l_jours
]
391 l_jourst2
= [convertit_age_vers_texte(d
) for d
in l_jourst
]
392 gros_dico
["data_j"] = l_jours2
393 gros_dico
["data_p"] = l_poids
394 gros_dico
["data_jours_taille"] = l_jourst2
395 gros_dico
["data_taille"] = l_taille
396 # gérer la date de naissance
397 if gros_dico
.get("naissance","") != "":
398 gros_dico
["naissance"] = convertit_date_vers_texte(gros_dico
["naissance"])
400 gros_dico
["maxi"] = convertit_age_vers_texte(gros_dico
["maxi"])
402 for clecouleur
in DEFAUT
["couleurs"]:
403 gros_dico
["couleurs"][clecouleur
] = tuple_vers_rgb(gros_dico
["couleurs"][clecouleur
])
405 # Enlever ce qui ne se sauvegarde pas si y'a
406 if "non_sauve" in gros_dico
:
407 del gros_dico
["non_sauve"]
409 return json
.dumps(gros_dico
, indent
=2,ensure_ascii
=False )
411 def fusionne_donnees(listes_jours
,listes_donnees
):
412 """ prend en argument deux dicos de listes. Chaque liste de jours est associée à une liste
413 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
414 ("age":truc, "donnee1":truc, "donnee2":truc, ...) triée par ordre de jours. Si jamais une des données est vide,
415 le champ du dictionnaire n'est pas rempli"""
418 """ teste si les listes sont toutes vides """
419 for l
in lj
.values():
425 """ renvoie la clé de la liste où il y a le min """
426 cle_mini
= CONFIG
["liste_typedonnees"][0]
427 for (cle
,liste
) in lj
.items():
428 if lj
[cle_mini
]== []:
431 if convertit_jours_vers_python(lj
[cle
][0],initialise_erreurs())<convertit_jours_vers_python(lj
[cle_mini
][0],initialise_erreurs()):
436 while not(fini(listes_jours
)):
437 typedonnee
= mini(listes_jours
)
438 # On extrait les données dans les deux listes (jours et données)
439 jour
= listes_jours
[typedonnee
].pop(0)
440 donnee
= listes_donnees
[typedonnee
].pop(0)
441 if liste_f
== [] or jour
!= liste_f
[-1]["age"]: # Si le jour est un "nouveau" jour
442 liste_f
.append({"age":jour}
)
443 # On met à jour l'élément
444 liste_f
[-1][typedonnee
] = donnee
449 ### COnversion json vers formulaire
450 # Json -> formulaire HTML
451 def fichier_json_vers_configdonnees(chaine
,liste_err
):
452 """ prend le json importé (chaine) et l'exporte vers les valeurs du formulaire
453 Renvoyé sous forme de dictionnaire (mais adapté au formulaire web)"""
454 debug("json vers config : Prêt à interpréter le json",liste_err
)
456 valform
= json
.loads(chaine
)
458 erreur("Impossible de lire le fichier json !",liste_err
)
460 # Il faut maintenant récupérer les l_jours et l_poids puis les remettre
461 # sous forme de age_i et poids_i
465 for typed
in CONFIG
["liste_typedonnees"]:
466 if typed
== "poids": # pour la rétrocompatibilité
467 listes_jours
[typed
] = valform
.get("data_j",[])
468 listes_donnees
[typed
] = valform
.get("data_p",[])
470 listes_jours
[typed
] = valform
.get("data_jours_"+typed
,[])
471 listes_donnees
[typed
] = valform
.get("data_"+typed
,[])
473 debug("Avant fusion : listes jours "+str(listes_jours
),liste_err
)
474 liste_donnees
= fusionne_donnees(listes_jours
,listes_donnees
)
475 debug("Fusion de listes ok. Liste moche : "+str(liste_donnees
),liste_err
)
476 for i
in range(len(liste_donnees
)):
477 for (cle
,val
) in liste_donnees
[i
].items():
478 valform
[cle
+"_"+str(i
)] = val
480 valform
["nb_data"] = max(len(liste_donnees
) +2,DEFAUT
["nb_data"])