]> git.immae.eu Git - perso/Denise/oms.git/blame - gestion_donnees.py
bug corrigé (ajout de lignes) + taille cachée par défaut.
[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 """
24 chainenombre = ""
25 agejours = 0.
26 for lettre in chaine:
27 if lettre.isdigit():
28 chainenombre += lettre
29 else:
30 if lettre == 'a' or lettre == 'A':
31 # On a trouvé l'année, on ajoute tout ce qui est trouvé jusque là
d03279e7 32 agejours += int(chainenombre)*CONFIG["jours_dans_annee"]
5679dfd0
DL
33 chainenombre = ""
34 elif lettre == 'm' or lettre == 'M':
35 # On a trouvé le mois
d03279e7 36 agejours += int(chainenombre)*CONFIG["jours_dans_mois"]
5679dfd0
DL
37 chainenombre = ""
38 elif lettre == 's' or lettre == 'S':
39 # la semaine
d03279e7 40 agejours += int(chainenombre)*CONFIG["jours_dans_semaine"]
5679dfd0
DL
41 chainenombre = ""
42 elif lettre == 'j' or lettre == 'J':
43 # On a trouvé le jour
44 agejours += int(chainenombre)
45 chainenombre = ""
46 elif lettre != ' ':
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 != "":
be2bf515 51 agejours += int(chainenombre)
5679dfd0
DL
52 if agejours<0:
53 warning("L'âge est négatif !",liste_err)
54 agejours = 0
be2bf515 55 return agejours
5679dfd0 56
8b5845ff 57# python -> json
5679dfd0
DL
58def 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"""
d03279e7
DL
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"])
5679dfd0
DL
65
66 chaine = ""
67 if annees >0:
68 chaine += str(annees)+"a"
69 if mois >0:
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"
73 return chaine
74##########################
be2bf515 75
8b5845ff
DL
76# fonction qui calcule "auto" le maxi du graphique en fonction du max
77def calcule_max_graphique(l_jours):
78 """ calcule l'age maxi sur le graphique"""
79 if l_jours == []:
80 return CONFIG["jours_defaut_donneesvides"]
81 else:
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
84 return jour_maxi
85
be2bf515
DL
86
87def 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"""
91 chaine2 = ""
92 for l in chaine:
93 if l.isalpha():
94 chaine2+=l
95 chaine2 = unidecode.unidecode(chaine2)
96 return chaine2[:15]
5679dfd0 97
8b5845ff
DL
98def 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."""
5679dfd0
DL
102 chaine2 = chaine.replace(",",".")
103 chaine2 = chaine2.replace(" ","")
104
105 try:
8b5845ff 106 donnee = float(chaine2)
5679dfd0 107 except:
8b5845ff
DL
108 warning(typedonnee+" impossible à lire : "+chaine,liste_err)
109 donnee = 0
110 if not( 0<=donnee<CONFIG[typedonnee+"_maxi"]):
111 warning(typedonnee+"incohérent(e) : "+str(donnee),liste_err)
112 donnee = 0
113 return donnee
5679dfd0 114
5679dfd0
DL
115
116#########################
117
8b5845ff 118# web -> python
5679dfd0
DL
119def 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("-")
124 if len(liste) != 3:
125 warning("La date : "+chaine+" est invalide !",liste_err)
126 return ""
127 else:
30158504 128 debug("Conversion de la date "+chaine+". Découpage : "+str(liste),liste_err)
9cb3c31c
DL
129 try:
130 date = datetime.date(int(liste[0]),int(liste[1]),int(liste[2]))
131 except:
132 date = ""
30158504 133 warning("Impossible de lire la date "+chaine+". Format accepté : aaaa-mm-jj",liste_err)
9cb3c31c 134 return date
8b5845ff
DL
135
136# python -> json
5679dfd0
DL
137def convertit_date_vers_texte(date):
138 """ convertit une date python en format texte aaaa-mm-jj"""
139 if date == "":
140 return ""
141 else:
142 return (str(date.year)+"-"+str(date.month)+"-"+str(date.day))
143
144
145def 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
149 jours = d.days
150 if jours<0:
151 erreur_continue("La différence entre les dates est négative... :/")
152 return -1
153 return jours
154
155
8b5845ff
DL
156################### On regroupe tout ce qui gère les données en une fonction
157
158def 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"""
161
162 # Régler la configuration
163 config = gere_configuration(data,liste_err)
164
165 # récupérer les données
166 listes_jours = {}
167 listes_donnees = {}
168 for typed in CONFIG["liste_typedonnees"]:
169 listes_jours[typed],listes_donnees[typed] = gere_donnees(data,config["naissance"],typed,liste_err)
170
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"])
175
176 return (config,listes_jours,listes_donnees)
177
178
179
5679dfd0
DL
180########### Fonctions qui gèretn les données
181
8b5845ff
DL
182def 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"""
185 if chaine == "on":
186 return "oui"
187 else:
188 return ""
5679dfd0
DL
189
190def gere_configuration(data,liste_err):
be2bf515 191 """ prend en argument le dictionnaire de requête (configuration imparfaite), et
5679dfd0
DL
192 construit le dictionnaire de configuration qui va bien.
193 Vérifie que chaque entrée est cohérente évidemment."""
194 configuration = {}
195
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
d03279e7 199 configuration["nom"] = nom[:CONFIG["longueur_max_nom_bebe"]]
5679dfd0
DL
200
201 sexe = data.get("sexe","")
a46e1269 202 if not (sexe in ["F","M","N"]):
5679dfd0 203 warning("Le sexe de l'enfant est invalide ! "+sexe,liste_err)
a46e1269 204 sexe = "N"
5679dfd0
DL
205 configuration["sexe"] = sexe
206
207 naissance = data.get("naissance","")
208 if naissance !="":
209 naissance = convertit_date_vers_python(naissance,liste_err)
210 configuration["naissance"] = naissance
211
212 # Type de courbe. Au pire on met P
213 tyc = data.get("typecourbe","")
214 if not (tyc in ["P","Z"]):
215 tyc = "P"
216 configuration["typecourbe"] = tyc
217
218 # unité
219 unite = data.get("unite","")
9e4c51c7 220 if not (unite in CONFIG["liste_unites"]):
5679dfd0
DL
221 unite = ""
222 #warning("L'unité "+unite+" n'est pas reconnue !",liste_err)
223 configuration["unite"] = unite
9e4c51c7 224
5679dfd0 225 # grille
8b5845ff
DL
226 configuration["grille"] = gere_checkbox(data.get("grille",""))
227
228 # tracer ou non les courbes vides
229 configuration["tracevide"] = gere_checkbox(data.get("tracevide",""))
5679dfd0 230
8b5845ff
DL
231 # Même échelle sur tous les graphiques
232 configuration["memechelle"] = gere_checkbox(data.get("memechelle",""))
233
5679dfd0
DL
234 # maxi. 0 signifie qu'on veut pas de maxi
235 maxi = data.get("maxi","")
236 if maxi == "":
237 configuration["maxi"] = 0
238 else:
54c71831 239 configuration["maxi"] = int(convertit_jours_vers_python(maxi,liste_err))
5679dfd0
DL
240
241 # dimensions du graphique
be2bf515 242 largeur = data.get("largeur","")
5679dfd0 243 if largeur == "":
915e90bb 244 largeur = DEFAUT["largeur_graphique"]
5679dfd0
DL
245 else:
246 try:
247 largeur = int(largeur)
248 except:
249 warning("La largeur "+largeur+"est invalide !",liste_err)
915e90bb 250 largeur = DEFAUT["largeur_graphique"]
d03279e7
DL
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)
5679dfd0
DL
257 configuration["largeur"] = largeur
258
be2bf515 259 hauteur = data.get("hauteur","")
5679dfd0 260 if hauteur == "":
915e90bb 261 hauteur = DEFAUT["hauteur_graphique"]
5679dfd0
DL
262 else:
263 try:
264 hauteur = int(hauteur)
265 except:
266 warning("La hauteur "+hauteur+"est invalide !",liste_err)
915e90bb 267 hauteur = DEFAUT["hauteur_graphique"]
d03279e7
DL
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)
5679dfd0
DL
274 configuration["hauteur"] = hauteur
275
276 # existence et position de la légende
8b5845ff 277 configuration["legende"] = gere_checkbox(data.get("legende",""))
5679dfd0
DL
278
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
61020126 283
fd69b6b5 284
915e90bb
DL
285 configuration["couleurs"] = {}
286 # gérer les couleurs
915e90bb
DL
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
fd69b6b5 290
8b5845ff
DL
291 # On y ajoute la partie "non sauvée" qui servira peut-être plus tard
292 configuration["non_sauve"] = {}
fd69b6b5 293
5679dfd0 294 return configuration
5679dfd0
DL
295
296
8b5845ff
DL
297def 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)
305 return ([],[])
306
307 # On construit une liste de couples d'abord
5679dfd0
DL
308 liste_donnees = []
309
310 i = 0
8b5845ff
DL
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)
5679dfd0
DL
315 age = data.get("age_"+str(i),"")
316 if age !="":
317 age = convertit_jours_vers_python(age,liste_err)
8b5845ff 318 liste_donnees.append((age,donnee))
5679dfd0
DL
319 else:
320 date = data.get("date_"+str(i),"")
321 datep = convertit_date_vers_python(date,liste_err)
322 # on vérifie la date
323 if naissance == "":
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)
8b5845ff 327 liste_donnees.append((age,donnee))
5679dfd0
DL
328 i+=1
329
330 # Trier la liste
331 liste_donnees.sort(key=lambda x : x[0])
332
333 # splitter la liste
334 l_jours = [x[0] for x in liste_donnees]
8b5845ff 335 l_donnee = [x[1] for x in liste_donnees]
5679dfd0 336
8b5845ff 337 return (l_jours,l_donnee)
be2bf515
DL
338
339
8b5845ff 340#### export vers json
be2bf515 341
8b5845ff 342def donnees_vers_json(l_jours,l_poids,l_jourst,l_taille,config):
be2bf515 343 """ retourne le json à renvoyer"""
915e90bb 344 gros_dico = copy.deepcopy(config)
be2bf515 345 l_jours2 = [convertit_age_vers_texte(d) for d in l_jours]
8b5845ff 346 l_jourst2 = [convertit_age_vers_texte(d) for d in l_jourst]
be2bf515
DL
347 gros_dico["data_j"] = l_jours2
348 gros_dico["data_p"] = l_poids
8b5845ff
DL
349 gros_dico["data_jours_taille"] = l_jourst2
350 gros_dico["data_taille"] = l_taille
be2bf515
DL
351 # gérer la date de naissance
352 if gros_dico.get("naissance","") != "":
353 gros_dico["naissance"] = convertit_date_vers_texte(gros_dico["naissance"])
354 # gérer l'age maxi
355 gros_dico["maxi"] = convertit_age_vers_texte(gros_dico["maxi"])
fd69b6b5 356 # gérer les couleurs
915e90bb
DL
357 for clecouleur in DEFAUT["couleurs"]:
358 gros_dico["couleurs"][clecouleur] = tuple_vers_rgb(gros_dico["couleurs"][clecouleur])
fd69b6b5 359
8b5845ff
DL
360 # Enlever ce qui ne se sauvegarde pas si y'a
361 if "non_sauve" in gros_dico:
362 del gros_dico["non_sauve"]
5679dfd0 363
be2bf515 364 return json.dumps(gros_dico, indent=2,ensure_ascii=False )
5679dfd0 365
8b5845ff
DL
366def 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)
372 def fini(lj):
373 """ teste si les listes sont toutes vides """
374 for l in lj.values():
375 if l!=[]:
376 return False
377 return True
378
379 def mini(lj):
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]== []:
384 cle_mini = cle
385 elif lj[cle] != []:
386 if convertit_jours_vers_python(lj[cle][0],[])<convertit_jours_vers_python(lj[cle_mini][0],[]):
387 cle_mini = cle
388 return cle_mini
389
390 liste_f = []
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
400
401 return liste_f
402
5679dfd0 403
8b5845ff
DL
404
405# Json -> formulaire HTML
d03279e7
DL
406def 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)
409 try:
410 valform = json.loads(chaine)
411 except :
412 erreur("Impossible de lire le fichier json !",liste_err)
413 return {}
be2bf515
DL
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
8b5845ff
DL
416
417 listes_jours = {}
418 listes_donnees = {}
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",[])
423 else:
424 listes_jours[typed] = valform.get("data_jours_"+typed,[])
425 listes_donnees[typed] = valform.get("data_"+typed,[])
426
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
be2bf515 433
8b5845ff 434 valform["nb_data"] = max(len(liste_donnees) +2,DEFAUT["nb_data"])
5679dfd0 435
be2bf515 436 return valform
5679dfd0 437
5679dfd0 438
be2bf515 439