X-Git-Url: https://git.immae.eu/?p=perso%2FDenise%2Foms.git;a=blobdiff_plain;f=gestion_donnees.py;h=123120b9a514f265d1dd1e53d28908656e532cb0;hp=a2d0a1e2b819431a83391243e9764a62613dd7ff;hb=cf0d4c8c36224ca7059e99d03c25abaf694011d7;hpb=be2bf5155489b103e616845ffedb1a58c3808c48 diff --git a/gestion_donnees.py b/gestion_donnees.py index a2d0a1e..123120b 100644 --- a/gestion_donnees.py +++ b/gestion_donnees.py @@ -1,12 +1,17 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -from configuration import * -from gestion_erreurs import * +from configuration import CONFIG,DEFAUT +from gestion_erreurs import debug, warning, erreur, initialise_erreurs +from gestion_couleurs import rgb_vers_tuple, tuple_vers_rgb +from gestion_unites import choix_unite import datetime import json import unidecode +import copy +### Les données "tournent" selon : +### python -> json -> (export/import) -> formulaire HTML -> données POST -> python etc ############ Fonctions de conversion @@ -16,6 +21,7 @@ def convertit_jours_vers_python(chaine,liste_err): Si un des caractères n'est ni un nombre, ni une lettre "autorisée" ni une espace, on affiche un warning et on ignore ce caractère """ +# debug("conversion de "+chaine+" vers un nb de jours",liste_err) chainenombre = "" agejours = 0. for lettre in chaine: @@ -24,15 +30,15 @@ def convertit_jours_vers_python(chaine,liste_err): else: if lettre == 'a' or lettre == 'A': # On a trouvé l'année, on ajoute tout ce qui est trouvé jusque là - agejours += int(chainenombre)*jours_dans_annee + agejours += int(chainenombre)*CONFIG["jours_dans_annee"] chainenombre = "" elif lettre == 'm' or lettre == 'M': # On a trouvé le mois - agejours += int(chainenombre)*jours_dans_mois + agejours += int(chainenombre)*CONFIG["jours_dans_mois"] chainenombre = "" elif lettre == 's' or lettre == 'S': # la semaine - agejours += int(chainenombre)*jours_dans_semaine + agejours += int(chainenombre)*CONFIG["jours_dans_semaine"] chainenombre = "" elif lettre == 'j' or lettre == 'J': # On a trouvé le jour @@ -47,15 +53,17 @@ def convertit_jours_vers_python(chaine,liste_err): if agejours<0: warning("L'âge est négatif !",liste_err) agejours = 0 +# debug("On a convertit ! Résultat : "+str(agejours),liste_err) return agejours +# python -> json def convertit_age_vers_texte(nombre): """ convertit un nombre de jours en un truc plus lisible en mois, années, jours et renvoie une chaîne sous la forme 3a2m1j par exemple""" - annees = int(nombre / jours_dans_annee) - restant = nombre - annees*jours_dans_annee - mois = int(restant/jours_dans_mois) - jours= round(nombre - mois*jours_dans_mois - annees*jours_dans_annee) + annees = int(nombre / CONFIG["jours_dans_annee"]) + restant = nombre - annees*CONFIG["jours_dans_annee"] + mois = int(restant/CONFIG["jours_dans_mois"]) + jours= round(nombre - mois*CONFIG["jours_dans_mois"] - annees*CONFIG["jours_dans_annee"]) chaine = "" if annees >0: @@ -65,8 +73,19 @@ def convertit_age_vers_texte(nombre): if jours>0 or nombre ==0: # si c'est la naissance, faut beien écrire 0j quand même chaine += str(jours)+"j" return chaine + ########################## +# fonction qui calcule "auto" le maxi du graphique en fonction du max +def calcule_max_graphique(l_jours): + """ calcule l'age maxi sur le graphique""" + if l_jours == []: + return CONFIG["jours_defaut_donneesvides"] + else: + jour_maxi = max(l_jours)# pas la peine d'aller très au delà du jour max + jour_maxi = int(jour_maxi* 1.2)+3 # on rajoute un peu + return jour_maxi + def simplifie_nom(chaine): """ simplifie le nom chaine afin d'en faire une extension @@ -79,29 +98,27 @@ def simplifie_nom(chaine): chaine2 = unidecode.unidecode(chaine2) return chaine2[:15] -def convertit_poids_vers_python(chaine,liste_err): - """ convertit une chaine vers un float qui est le poids. - On gère notamment la virgule, et on enlève les espaces - Un poids invalide -> on renvoie 0 avec un warning""" +def convertit_donnee_vers_python(chaine,typedonnee,liste_err): + """ convertit une chaine vers un float qui est le type donnee voulu. + La virgule peut être . ou , et on vire d'éventuels espaces. + Taille invalide : on renvoie 0 avec un warning.""" chaine2 = chaine.replace(",",".") chaine2 = chaine2.replace(" ","") try: - poids = float(chaine2) + donnee = float(chaine2) except: - warning("Poids impossible à lire : "+chaine,liste_err) - poids = 0 - if not( 0<=poids python def convertit_date_vers_python(chaine,liste_err): """ prend une chaine comme renvoyée par un champ de formulaire date aaaa-mm-jj et en fait une date python @@ -111,13 +128,15 @@ def convertit_date_vers_python(chaine,liste_err): warning("La date : "+chaine+" est invalide !",liste_err) return "" else: + debug("Conversion de la date "+chaine+". Découpage : "+str(liste),liste_err) try: date = datetime.date(int(liste[0]),int(liste[1]),int(liste[2])) except: date = "" - warning("Impossible de lire la date "+chaine,liste_err) + warning("Impossible de lire la date "+chaine+". Format accepté : aaaa-mm-jj",liste_err) return date - + +# python -> json def convertit_date_vers_texte(date): """ convertit une date python en format texte aaaa-mm-jj""" if date == "": @@ -132,24 +151,56 @@ def delta_date(date1,datenaissance): d = date1 - datenaissance jours = d.days if jours<0: - erreur_continue("La différence entre les dates est négative... :/") + warning("La différence entre les dates est négative... :/") return -1 return jours -########### Fonctions qui gèretn les données +################### On regroupe tout ce qui gère les données en une fonction + +def web_vers_python(data,liste_err): + """ prend en argument le dictionnaire de requête et renvoie la config, et les + tableaux de donnée""" + + # Régler la configuration + config = gere_configuration(data,liste_err) + + # récupérer les données + listes_jours = {} + listes_donnees = {} + for typed in CONFIG["liste_typedonnees"]: + listes_jours[typed],listes_donnees[typed] = gere_donnees(data,config["naissance"],typed,liste_err) + + # Si on a choisi la même échelle de données + if config["memechelle"] == "oui": + config["non_sauve"]["maxi"] = calcule_max_graphique([j for lj in listes_jours.values() for j in lj]) + config["non_sauve"]["unite"] = choix_unite(config["non_sauve"]["maxi"]) + + return (config,listes_jours,listes_donnees) + + +########### Fonctions qui gèretn les données web vers python + +def gere_checkbox(chaine): + """ prend en arg une chaine, et renvoie "oui" si c'est "on" (sortie de la checkbox) + et chaîne vide si n'importe quoi d'autre""" + if chaine == "on": + return "oui" + else: + return "" def gere_configuration(data,liste_err): """ prend en argument le dictionnaire de requête (configuration imparfaite), et construit le dictionnaire de configuration qui va bien. Vérifie que chaque entrée est cohérente évidemment.""" - configuration = {} + # Initialisation + configuration = {"non_sauve": {}} # Pour le nom, osef qu'il soit vide nom = data.get("nom","") # Par contre s'il est trop long on le tronque - configuration["nom"] = nom[:longueur_max_nom_bebe] + configuration["nom"] = nom[:CONFIG["longueur_max_nom_bebe"]] sexe = data.get("sexe","") if not (sexe in ["F","M","N"]): @@ -162,6 +213,12 @@ def gere_configuration(data,liste_err): naissance = convertit_date_vers_python(naissance,liste_err) configuration["naissance"] = naissance + prematurite = data.get("prematurite","") + j = convertit_jours_vers_python(prematurite,liste_err) + configuration["prematurite"] = convertit_age_vers_texte(j) + + configuration["agecorrige"] = gere_checkbox(data.get("agecorrige","")) + # Type de courbe. Au pire on met P tyc = data.get("typecourbe","") if not (tyc in ["P","Z"]): @@ -170,106 +227,135 @@ def gere_configuration(data,liste_err): # unité unite = data.get("unite","") - if not (unite in liste_unites_valides): + if not (unite in CONFIG["liste_unites"]): unite = "" #warning("L'unité "+unite+" n'est pas reconnue !",liste_err) configuration["unite"] = unite - + # grille - grille = data.get("grille","") - if grille != "on": - configuration["grille"] = "" - else: - configuration["grille"] = "oui" + configuration["grille"] = gere_checkbox(data.get("grille","")) + + # tracer ou non les courbes vides + configuration["tracevide"] = gere_checkbox(data.get("tracevide","")) + # Même échelle sur tous les graphiques + configuration["memechelle"] = gere_checkbox(data.get("memechelle","")) + + # maxi. 0 signifie qu'on veut pas de maxi maxi = data.get("maxi","") if maxi == "": configuration["maxi"] = 0 else: - configuration["maxi"] = convertit_jours_vers_python(maxi,liste_err) + configuration["maxi"] = int(convertit_jours_vers_python(maxi,liste_err)) # dimensions du graphique largeur = data.get("largeur","") if largeur == "": - largeur = largeur_graphique + largeur = DEFAUT["largeur_graphique"] else: try: largeur = int(largeur) except: warning("La largeur "+largeur+"est invalide !",liste_err) - largeur = largeur_graphique - if largeur > largeur_graphique_max: - largeur = largeur_graphique_max - warning("Largeur trop grande !",liste_err) - elif largeur < largeur_graphique_min: - largeur = largeur_graphique_min - warning("Largeur trop petite !",liste_err) + largeur = DEFAUT["largeur_graphique"] + if largeur > CONFIG["largeur_graphique_max"]: + largeur = CONFIG["largeur_graphique_max"] + warning("Largeur du graphique trop grande !",liste_err) + elif largeur < CONFIG["largeur_graphique_min"]: + largeur = CONFIG["largeur_graphique_min"] + warning("Largeur du graphique trop petite !",liste_err) configuration["largeur"] = largeur hauteur = data.get("hauteur","") if hauteur == "": - hauteur = hauteur_graphique + hauteur = DEFAUT["hauteur_graphique"] else: try: hauteur = int(hauteur) except: warning("La hauteur "+hauteur+"est invalide !",liste_err) - hauteur = hauteur_graphique - if hauteur > hauteur_graphique_max: - hauteur = hauteur_graphique_max - warning("Hauteur trop grande !",liste_err) - elif hauteur < hauteur_graphique_min: - hauteur = hauteur_graphique_min - warning("Hauteur trop petite !",liste_err) + hauteur = DEFAUT["hauteur_graphique"] + if hauteur > CONFIG["hauteur_graphique_max"]: + hauteur = CONFIG["hauteur_graphique_max"] + warning("Hauteur du graphique trop grande !",liste_err) + elif hauteur < CONFIG["hauteur_graphique_min"]: + hauteur = CONFIG["hauteur_graphique_min"] + warning("Hauteur du graphique trop petite !",liste_err) configuration["hauteur"] = hauteur # existence et position de la légende - legende = data.get("legende","") - if legende =="": - legende = "non" - elif legende=="on": - legende = "oui" - else: - legende = "oui" - configuration["legende"] = legende + configuration["legende"] = gere_checkbox(data.get("legende","")) positionlegende = data.get("positionlegende","") if not(positionlegende in ['upper left','upper right','lower left','lower right']): positionlegende = "upper left" configuration["positionlegende"] = positionlegende - return configuration + configuration["couleurs"] = {} + # gérer les couleurs + for clecouleur in DEFAUT["couleurs"]: + coul = rgb_vers_tuple(data.get("couleur_"+clecouleur,""),CONFIG["couleurs"][clecouleur],liste_err) + configuration["couleurs"][clecouleur] = coul + + + configuration["non_sauve"]["grilleamelio"] = gere_checkbox(data.get("grilleamelio","")) -#def configuration_vers_texte(config): -# """ exporte le texte associé à une configuration -# on dumpe simplement sauf pour maxi """ -# texte = "# Section configuration\n" -# for (cle,val) in config.items(): -# if cle != "maxi": -# texte+= cle + "=" + str(val) + "\n" -# else: -# texte+= cle + "=" + convertit_age_vers_texte(val)+"\n" -# texte +="\n" -# return texte - -def gere_donneespoids(data,naissance,liste_err): - """ prend en argument le dictionnaire de requête, et la date de naissance - (éventuellement vide) et construit les deux listes l_jours et l_poids""" + #### La partie extrapolation n'a pas besoin d'être sauvée + configuration["non_sauve"]["prolongercourbes"] = gere_checkbox(data.get("prolongercourbes","")) + + # Valeur par défaut : 1 + debug(data.get("nbextradata", "aaargh"), liste_err) + nbextradata = data.get("nbextradata",1) + try: + nbextradata = int(nbextradata) + except: + warning("Le nombre de données sur lequel on extrapole est invalide : "+nbextradata, liste_err) + nbextradata = 1 + configuration["non_sauve"]["nbextradata"] = nbextradata + + if data.get("calculextradata_type","") in CONFIG["liste_typedonnees"]: + configuration["non_sauve"]["calculextradata_type"] = data.get("calculextradata_type","") + configuration["non_sauve"]["calculextradata_age"] = convertit_jours_vers_python(data.get("calculextradata_age","0j"),liste_err) + else: + configuration["non_sauve"]["calculextradata_type"] = "" + # On ne met rien dans l'âge, pas la peine + + ctyped = data.get("calculextratemps_type","") + if ctyped in CONFIG["liste_typedonnees"]: + configuration["non_sauve"]["calculextratemps_type"] = ctyped + configuration["non_sauve"]["calculextratemps_val"] = convertit_donnee_vers_python(data.get("calculextratemps_val",""), ctyped, liste_err) + else: + configuration["non_sauve"]["calculextratemps_type"] = "" - # On construit la liste des couples + return configuration + + + +## web vers python : données +def gere_donnees(data,naissance,typedonnee,liste_err): + """ prend en argument le dictionnaire de requête, et la date de + naissance (éventuellement vide), et construit deux listes : + l_jours et l_data correspondantes. + Il faut donner en argument le type de données : voir + CONFIG["liste_typedonnees"]""" + if typedonnee not in CONFIG["liste_typedonnees"]: + warning("gere_donnees : le type de données : "+typedonnee+" est invalide !! Types acceptés : "+str(CONFIG["liste_typedonnees"]),liste_err) + return ([],[]) + + # On construit une liste de couples d'abord liste_donnees = [] i = 0 - # On va chercher si y'a des données à poids_i - while "poids_"+str(i) in data.keys(): - if data["poids_"+str(i)] != "": - poids = convertit_poids_vers_python(data["poids_"+str(i)],liste_err) + # On va chercher si y'a des données à donnee_i + while typedonnee+"_"+str(i) in data.keys(): + if data[typedonnee+"_"+str(i)] != "": + donnee = convertit_donnee_vers_python(data[typedonnee+"_"+str(i)],typedonnee,liste_err) age = data.get("age_"+str(i),"") if age !="": age = convertit_jours_vers_python(age,liste_err) - liste_donnees.append((age,poids)) + liste_donnees.append((age,donnee)) else: date = data.get("date_"+str(i),"") datep = convertit_date_vers_python(date,liste_err) @@ -278,7 +364,7 @@ def gere_donneespoids(data,naissance,liste_err): 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) elif datep != "": # la date est valide et on a une date de naissance age = delta_date(datep,naissance) - liste_donnees.append((age,poids)) + liste_donnees.append((age,donnee)) i+=1 # Trier la liste @@ -286,90 +372,108 @@ def gere_donneespoids(data,naissance,liste_err): # splitter la liste l_jours = [x[0] for x in liste_donnees] - l_poids = [x[1] for x in liste_donnees] - - return (l_jours,l_poids) - + l_donnee = [x[1] for x in liste_donnees] -#def donnees_poids_vers_texte(l_jours,l_poids): -# """ retourne le texte correspondant aux données de poids """ -# texte = "# Section données\n" -# -# for i in range(len(l_poids)): -# texte +=convertit_age_vers_texte(l_jours[i])+","+convertit_poids_vers_texte(l_poids[i])+"\n" -# -# texte+="\n" -# return texte + return (l_jours,l_donnee) +# python vers Json +#### export vers json - - -def donnees_vers_json(l_jours,l_poids,config): +def donnees_vers_json(l_jours,l_poids,l_jourst,l_taille,config): """ retourne le json à renvoyer""" - gros_dico = config.copy() + gros_dico = copy.deepcopy(config) l_jours2 = [convertit_age_vers_texte(d) for d in l_jours] + l_jourst2 = [convertit_age_vers_texte(d) for d in l_jourst] gros_dico["data_j"] = l_jours2 gros_dico["data_p"] = l_poids + gros_dico["data_jours_taille"] = l_jourst2 + gros_dico["data_taille"] = l_taille # gérer la date de naissance if gros_dico.get("naissance","") != "": gros_dico["naissance"] = convertit_date_vers_texte(gros_dico["naissance"]) # gérer l'age maxi gros_dico["maxi"] = convertit_age_vers_texte(gros_dico["maxi"]) + # gérer les couleurs + for clecouleur in DEFAUT["couleurs"]: + gros_dico["couleurs"][clecouleur] = tuple_vers_rgb(gros_dico["couleurs"][clecouleur]) + + # Enlever ce qui ne se sauvegarde pas si y'a + if "non_sauve" in gros_dico: + del gros_dico["non_sauve"] return json.dumps(gros_dico, indent=2,ensure_ascii=False ) -#def fichier_texte_vers_configdonnees(fichier,liste_err): -# """ prend le texte importé et l'exporte vers configuration et données -# sous forme de valeurs du formulaire """ -# -# valform = {} -# indice_formulaire = 0 # l'indice du formulaire pour les données : age_1, date_1, poids_1 etc -# num_ligne = 0 -# lignes = fichier.readlines() -# for ligne in lignes: -# num_ligne +=1 -# ligne = str(ligne,"utf8") -# ligne = ligne.rstrip("\n") -# if ligne != "" and ligne[0] != "#" and not(ligne.isspace()): # les lignes commençant par # sont des commentaires -# # On essaie de partitionner pour voir -# (var,egal,val) = ligne.partition("=") -# if egal == "=": # c'est une ligne de config -# valform[var] = val -# else: -# (age,virgule,poids) = ligne.partition(",") # On partitionne avec , -# if virgule == ",": -# # c'est une ligne de data -# valform["age_"+str(indice_formulaire)] = age -# valform["poids_"+str(indice_formulaire)] = poids -# indice_formulaire +=1 -# -# else: -# warning("La ligne "+str(num_ligne)+" n'est pas reconnue et sera ignorée : <"+ligne+">",liste_err) -# -# #le nb max du formulaire -# valform["nb_data"] = max(indice_formulaire +2,nombre_lignes_form) -# -# return valform - -def fichier_json_vers_configdonnees(fichier,liste_err): - """ prend le json importé et l'exporte vers les valeurs du formulaire """ - chaine = fichier.read() - valform = json.loads(chaine) +def fusionne_donnees(listes_jours,listes_donnees): + """ prend en argument deux dicos de listes. Chaque liste de jours est associée à une liste + 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 + ("age":truc, "donnee1":truc, "donnee2":truc, ...) triée par ordre de jours. Si jamais une des données est vide, + le champ du dictionnaire n'est pas rempli""" + + def fini(lj): + """ teste si les listes sont toutes vides """ + for l in lj.values(): + if l!=[]: + return False + return True + + def mini(lj): + """ renvoie la clé de la liste où il y a le min """ + cle_mini = CONFIG["liste_typedonnees"][0] + for (cle,liste) in lj.items(): + if lj[cle_mini]== []: + cle_mini = cle + elif lj[cle] != []: + if convertit_jours_vers_python(lj[cle][0],initialise_erreurs()) formulaire HTML +def fichier_json_vers_configdonnees(chaine,liste_err): + """ prend le json importé (chaine) et l'exporte vers les valeurs du formulaire + Renvoyé sous forme de dictionnaire (mais adapté au formulaire web)""" + debug("json vers config : Prêt à interpréter le json",liste_err) + try: + valform = json.loads(chaine) + except : + erreur("Impossible de lire le fichier json !",liste_err) + return {} # Il faut maintenant récupérer les l_jours et l_poids puis les remettre # sous forme de age_i et poids_i - l_jours= valform.get("data_j",[]) - l_poids=valform.get("data_p",[]) - if len(l_poids) != len(l_jours): - warning("Lecture du json : les données sont incohérentes (listes de taille différentes et/ou pb de lecture") - long = min(len(l_jours),len(l_poids)) - else: - long = len(l_jours) - for i in range(long): - valform["age_"+str(i)] = l_jours[i] - valform["poids_"+str(i)] = l_poids[i] + + listes_jours = {} + listes_donnees = {} + for typed in CONFIG["liste_typedonnees"]: + if typed == "poids": # pour la rétrocompatibilité + listes_jours[typed] = valform.get("data_j",[]) + listes_donnees[typed] = valform.get("data_p",[]) + else: + listes_jours[typed] = valform.get("data_jours_"+typed,[]) + listes_donnees[typed] = valform.get("data_"+typed,[]) + + debug("Avant fusion : listes jours "+str(listes_jours),liste_err) + liste_donnees = fusionne_donnees(listes_jours,listes_donnees) + debug("Fusion de listes ok. Liste moche : "+str(liste_donnees),liste_err) + for i in range(len(liste_donnees)): + for (cle,val) in liste_donnees[i].items(): + valform[cle+"_"+str(i)] = val - valform["nb_data"] = max(long +2,nombre_lignes_form) + valform["nb_data"] = max(len(liste_donnees) +2,DEFAUT["nb_data"]) return valform