]>
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 *
6 from gestion_couleurs
import *
7 from gestion_unites
import *
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
28 chainenombre
+= lettre
30 if lettre
== 'a' or lettre
== 'A':
31 # On a trouvé l'année, on ajoute tout ce qui est trouvé jusque là
32 agejours
+= int(chainenombre
)*CONFIG
["jours_dans_annee"]
34 elif lettre
== 'm' or lettre
== 'M':
36 agejours
+= int(chainenombre
)*CONFIG
["jours_dans_mois"]
38 elif lettre
== 's' or lettre
== 'S':
40 agejours
+= int(chainenombre
)*CONFIG
["jours_dans_semaine"]
42 elif lettre
== 'j' or lettre
== 'J':
44 agejours
+= int(chainenombre
)
47 # autre caractère : bizarre ?
48 warning("convertit_jour_vers_python : caractère invalide : "+lettre
,liste_err
)
49 # à la fin s'il reste qqch on le garde dans les jours
50 if chainenombre
!= "":
51 agejours
+= int(chainenombre
)
53 warning("L'âge est négatif !",liste_err
)
58 def convertit_age_vers_texte(nombre
):
59 """ convertit un nombre de jours en un truc plus lisible en mois, années, jours
60 et renvoie une chaîne sous la forme 3a2m1j par exemple"""
61 annees
= int(nombre
/ CONFIG
["jours_dans_annee"])
62 restant
= nombre
- annees
*CONFIG
["jours_dans_annee"]
63 mois
= int(restant
/CONFIG
["jours_dans_mois"])
64 jours
= round(nombre
- mois
*CONFIG
["jours_dans_mois"] - annees
*CONFIG
["jours_dans_annee"])
68 chaine
+= str(annees
)+"a"
70 chaine
+= str(mois
)+"m"
71 if jours
>0 or nombre
==0: # si c'est la naissance, faut beien écrire 0j quand même
72 chaine
+= str(jours
)+"j"
74 ##########################
76 # fonction qui calcule "auto" le maxi du graphique en fonction du max
77 def calcule_max_graphique(l_jours
):
78 """ calcule l'age maxi sur le graphique"""
80 return CONFIG
["jours_defaut_donneesvides"]
82 jour_maxi
= max(l_jours
)# pas la peine d'aller très au delà du jour max
83 jour_maxi
= int(jour_maxi
* 1.1)+3 # on rajoute un peu
87 def simplifie_nom(chaine
):
88 """ simplifie le nom chaine afin d'en faire une extension
89 pour le nom du fichier. Met tout en minuscules et vire les caractères spéciaux
90 et max 15 caractères"""
95 chaine2
= unidecode
.unidecode(chaine2
)
98 def convertit_donnee_vers_python(chaine
,typedonnee
,liste_err
):
99 """ convertit une chaine vers un float qui est le type donnee voulu.
100 La virgule peut être . ou , et on vire d'éventuels espaces.
101 Taille invalide : on renvoie 0 avec un warning."""
102 chaine2
= chaine
.replace(",",".")
103 chaine2
= chaine2
.replace(" ","")
106 donnee
= float(chaine2
)
108 warning(typedonnee
+" impossible à lire : "+chaine
,liste_err
)
110 if not( 0<=donnee
<CONFIG
[typedonnee
+"_maxi"]):
111 warning(typedonnee
+"incohérent(e) : "+str(donnee
),liste_err
)
116 #########################
119 def convertit_date_vers_python(chaine
,liste_err
):
120 """ prend une chaine comme renvoyée par un champ de formulaire date
121 aaaa-mm-jj et en fait une date python
122 renvoie "" si ne marche pas"""
123 liste
= chaine
.split("-")
125 warning("La date : "+chaine
+" est invalide !",liste_err
)
128 debug("Conversion de la date "+chaine
+". Découpage : "+str(liste
),liste_err
)
130 date
= datetime
.date(int(liste
[0]),int(liste
[1]),int(liste
[2]))
133 warning("Impossible de lire la date "+chaine
+". Format accepté : aaaa-mm-jj",liste_err
)
137 def convertit_date_vers_texte(date
):
138 """ convertit une date python en format texte aaaa-mm-jj"""
142 return (str(date
.year
)+"-"+str(date
.month
)+"-"+str(date
.day
))
145 def delta_date(date1
,datenaissance
):
146 """ renvoie le nombre de jours (entier) entre date1 et datenaissance format "datetime"
147 datenaissance est supposée antérieure. Erreur sinon."""
148 d
= date1
- datenaissance
151 erreur_continue("La différence entre les dates est négative... :/")
156 ################### On regroupe tout ce qui gère les données en une fonction
158 def web_vers_python(data
,liste_err
):
159 """ prend en argument le dictionnaire de requête et renvoie la config, et les
160 tableaux de donnée"""
162 # Régler la configuration
163 config
= gere_configuration(data
,liste_err
)
165 # récupérer les données
168 for typed
in CONFIG
["liste_typedonnees"]:
169 listes_jours
[typed
],listes_donnees
[typed
] = gere_donnees(data
,config
["naissance"],typed
,liste_err
)
171 # Si on a choisi la même échelle de données
172 if config
["memechelle"] == "oui":
173 config
["non_sauve"]["maxi"] = calcule_max_graphique([j
for lj
in listes_jours
.values() for j
in lj
])
174 config
["non_sauve"]["unite"] = choix_unite(config
["non_sauve"]["maxi"])
176 return (config
,listes_jours
,listes_donnees
)
180 ########### Fonctions qui gèretn les données
182 def gere_checkbox(chaine
):
183 """ prend en arg une chaine, et renvoie "oui" si c'est "on" (sortie de la checkbox)
184 et chaîne vide si n'importe quoi d'autre"""
190 def gere_configuration(data
,liste_err
):
191 """ prend en argument le dictionnaire de requête (configuration imparfaite), et
192 construit le dictionnaire de configuration qui va bien.
193 Vérifie que chaque entrée est cohérente évidemment."""
196 # Pour le nom, osef qu'il soit vide
197 nom
= data
.get("nom","")
198 # Par contre s'il est trop long on le tronque
199 configuration
["nom"] = nom
[:CONFIG
["longueur_max_nom_bebe"]]
201 sexe
= data
.get("sexe","")
202 if not (sexe
in ["F","M","N"]):
203 warning("Le sexe de l'enfant est invalide ! "+sexe
,liste_err
)
205 configuration
["sexe"] = sexe
207 naissance
= data
.get("naissance","")
209 naissance
= convertit_date_vers_python(naissance
,liste_err
)
210 configuration
["naissance"] = naissance
212 # Type de courbe. Au pire on met P
213 tyc
= data
.get("typecourbe","")
214 if not (tyc
in ["P","Z"]):
216 configuration
["typecourbe"] = tyc
219 unite
= data
.get("unite","")
220 if not (unite
in CONFIG
["liste_unites"]):
222 #warning("L'unité "+unite+" n'est pas reconnue !",liste_err)
223 configuration
["unite"] = unite
226 configuration
["grille"] = gere_checkbox(data
.get("grille",""))
228 # tracer ou non les courbes vides
229 configuration
["tracevide"] = gere_checkbox(data
.get("tracevide",""))
231 # Même échelle sur tous les graphiques
232 configuration
["memechelle"] = gere_checkbox(data
.get("memechelle",""))
234 # maxi. 0 signifie qu'on veut pas de maxi
235 maxi
= data
.get("maxi","")
237 configuration
["maxi"] = 0
239 configuration
["maxi"] = int(convertit_jours_vers_python(maxi
,liste_err
))
241 # dimensions du graphique
242 largeur
= data
.get("largeur","")
244 largeur
= DEFAUT
["largeur_graphique"]
247 largeur
= int(largeur
)
249 warning("La largeur "+largeur
+"est invalide !",liste_err
)
250 largeur
= DEFAUT
["largeur_graphique"]
251 if largeur
> CONFIG
["largeur_graphique_max"]:
252 largeur
= CONFIG
["largeur_graphique_max"]
253 warning("Largeur du graphique trop grande !",liste_err
)
254 elif largeur
< CONFIG
["largeur_graphique_min"]:
255 largeur
= CONFIG
["largeur_graphique_min"]
256 warning("Largeur du graphique trop petite !",liste_err
)
257 configuration
["largeur"] = largeur
259 hauteur
= data
.get("hauteur","")
261 hauteur
= DEFAUT
["hauteur_graphique"]
264 hauteur
= int(hauteur
)
266 warning("La hauteur "+hauteur
+"est invalide !",liste_err
)
267 hauteur
= DEFAUT
["hauteur_graphique"]
268 if hauteur
> CONFIG
["hauteur_graphique_max"]:
269 hauteur
= CONFIG
["hauteur_graphique_max"]
270 warning("Hauteur du graphique trop grande !",liste_err
)
271 elif hauteur
< CONFIG
["hauteur_graphique_min"]:
272 hauteur
= CONFIG
["hauteur_graphique_min"]
273 warning("Hauteur du graphique trop petite !",liste_err
)
274 configuration
["hauteur"] = hauteur
276 # existence et position de la légende
277 configuration
["legende"] = gere_checkbox(data
.get("legende",""))
279 positionlegende
= data
.get("positionlegende","")
280 if not(positionlegende
in ['upper left','upper right','lower left','lower right']):
281 positionlegende
= "upper left"
282 configuration
["positionlegende"] = positionlegende
285 configuration
["couleurs"] = {}
287 for clecouleur
in DEFAUT
["couleurs"]:
288 coul
= rgb_vers_tuple(data
.get("couleur_"+clecouleur
,""),CONFIG
["couleurs"][clecouleur
],liste_err
)
289 configuration
["couleurs"][clecouleur
] = coul
291 # On y ajoute la partie "non sauvée" qui servira peut-être plus tard
292 configuration
["non_sauve"] = {}
297 def gere_donnees(data
,naissance
,typedonnee
,liste_err
):
298 """ prend en argument le dictionnaire de requête, et la date de
299 naissance (éventuellement vide), et construit deux listes :
300 l_jours et l_data correspondantes.
301 Il faut donner en argument le type de données : voir
302 CONFIG["liste_typedonnees"]"""
303 if typedonnee
not in CONFIG
["liste_typedonnees"]:
304 warning("gere_donnees : le type de données : "+typedonnee
+" est invalide !! Types acceptés : "+str(CONFIG
["liste_typedonnees"]),liste_err
)
307 # On construit une liste de couples d'abord
311 # On va chercher si y'a des données à donnee_i
312 while typedonnee
+"_"+str(i
) in data
.keys():
313 if data
[typedonnee
+"_"+str(i
)] != "":
314 donnee
= convertit_donnee_vers_python(data
[typedonnee
+"_"+str(i
)],typedonnee
,liste_err
)
315 age
= data
.get("age_"+str(i
),"")
317 age
= convertit_jours_vers_python(age
,liste_err
)
318 liste_donnees
.append((age
,donnee
))
320 date
= data
.get("date_"+str(i
),"")
321 datep
= convertit_date_vers_python(date
,liste_err
)
324 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
)
325 elif datep
!= "": # la date est valide et on a une date de naissance
326 age
= delta_date(datep
,naissance
)
327 liste_donnees
.append((age
,donnee
))
331 liste_donnees
.sort(key
=lambda x
: x
[0])
334 l_jours
= [x
[0] for x
in liste_donnees
]
335 l_donnee
= [x
[1] for x
in liste_donnees
]
337 return (l_jours
,l_donnee
)
340 #### export vers json
342 def donnees_vers_json(l_jours
,l_poids
,l_jourst
,l_taille
,config
):
343 """ retourne le json à renvoyer"""
344 gros_dico
= copy
.deepcopy(config
)
345 l_jours2
= [convertit_age_vers_texte(d
) for d
in l_jours
]
346 l_jourst2
= [convertit_age_vers_texte(d
) for d
in l_jourst
]
347 gros_dico
["data_j"] = l_jours2
348 gros_dico
["data_p"] = l_poids
349 gros_dico
["data_jours_taille"] = l_jourst2
350 gros_dico
["data_taille"] = l_taille
351 # gérer la date de naissance
352 if gros_dico
.get("naissance","") != "":
353 gros_dico
["naissance"] = convertit_date_vers_texte(gros_dico
["naissance"])
355 gros_dico
["maxi"] = convertit_age_vers_texte(gros_dico
["maxi"])
357 for clecouleur
in DEFAUT
["couleurs"]:
358 gros_dico
["couleurs"][clecouleur
] = tuple_vers_rgb(gros_dico
["couleurs"][clecouleur
])
360 # Enlever ce qui ne se sauvegarde pas si y'a
361 if "non_sauve" in gros_dico
:
362 del gros_dico
["non_sauve"]
364 return json
.dumps(gros_dico
, indent
=2,ensure_ascii
=False )
366 def fusionne_donnees(listes_jours
,listes_donnees
):
367 """ prend en argument deux dicos de listes. Chaque liste de jours est associée à une liste
368 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
369 ("age":truc, "donnee1":truc, "donnee2":truc, ...) triée par ordre de jours. Si jamais une des données est vide,
370 le champ du dictionnaire n'est pas rempli"""
371 # nb_donnees = len(listes_jours)
373 """ teste si les listes sont toutes vides """
374 for l
in lj
.values():
380 """ renvoie la clé de la liste où il y a le min """
381 cle_mini
= CONFIG
["liste_typedonnees"][0]
382 for (cle
,liste
) in lj
.items():
383 if lj
[cle_mini
]== []:
386 if convertit_jours_vers_python(lj
[cle
][0],[])<convertit_jours_vers_python(lj
[cle_mini
][0],[]):
391 while not(fini(listes_jours
)):
392 typedonnee
= mini(listes_jours
)
393 # On extrait les données dans les deux listes (jours et données)
394 jour
= listes_jours
[typedonnee
].pop(0)
395 donnee
= listes_donnees
[typedonnee
].pop(0)
396 if liste_f
== [] or jour
!= liste_f
[-1]["age"]: # Si le jour est un "nouveau" jour
397 liste_f
.append({"age":jour}
)
398 # On met à jour l'élément
399 liste_f
[-1][typedonnee
] = donnee
405 # Json -> formulaire HTML
406 def fichier_json_vers_configdonnees(chaine
,liste_err
):
407 """ prend le json importé (chaine) et l'exporte vers les valeurs du formulaire """
408 debug("json vers config : Prêt à interpréter le json",liste_err
)
410 valform
= json
.loads(chaine
)
412 erreur("Impossible de lire le fichier json !",liste_err
)
414 # Il faut maintenant récupérer les l_jours et l_poids puis les remettre
415 # sous forme de age_i et poids_i
419 for typed
in CONFIG
["liste_typedonnees"]:
420 if typed
== "poids": # pour la rétrocompatibilité
421 listes_jours
[typed
] = valform
.get("data_j",[])
422 listes_donnees
[typed
] = valform
.get("data_p",[])
424 listes_jours
[typed
] = valform
.get("data_jours_"+typed
,[])
425 listes_donnees
[typed
] = valform
.get("data_"+typed
,[])
427 debug("Avant fusion : listes jours "+str(listes_jours
),liste_err
)
428 liste_donnees
= fusionne_donnees(listes_jours
,listes_donnees
)
429 debug("Fusion de listes ok. Liste moche : "+str(liste_donnees
),liste_err
)
430 for i
in range(len(liste_donnees
)):
431 for (cle
,val
) in liste_donnees
[i
].items():
432 valform
[cle
+"_"+str(i
)] = val
434 valform
["nb_data"] = max(len(liste_donnees
) +2,DEFAUT
["nb_data"])