X-Git-Url: https://git.immae.eu/?p=perso%2FDenise%2Foms.git;a=blobdiff_plain;f=gestion_donnees.py;h=137849d7e6701725c94efedcbcf8ecb57cc22345;hp=ea1f2593f625953852cf2e8655f83412cef60102;hb=64be08b2203c801c5231be30a2096790cd0c3b12;hpb=54c718310ef31192271125987b82ed1c281a3911 diff --git a/gestion_donnees.py b/gestion_donnees.py index ea1f259..137849d 100644 --- a/gestion_donnees.py +++ b/gestion_donnees.py @@ -2,8 +2,9 @@ # -*- coding: utf-8 -*- from configuration import CONFIG,DEFAUT -from gestion_erreurs import * -from gestion_couleurs import * +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 @@ -20,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: @@ -44,15 +46,17 @@ def convertit_jours_vers_python(chaine,liste_err): chainenombre = "" elif lettre != ' ': # autre caractère : bizarre ? - warning("convertit_jour_vers_python : caractère invalide : "+lettre,liste_err) + warning("problème à la conversion de "+chaine+". Caractère invalide : "+lettre,liste_err) # à la fin s'il reste qqch on le garde dans les jours if chainenombre != "": agejours += int(chainenombre) 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""" @@ -69,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 @@ -83,52 +98,64 @@ 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. + Si la chaine est en fait déjà au format float, on laisse tel quel""" + if type(chaine) == float: + return chaine 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 CONFIG["poids_maxi_conversion"]: + donnee = donnee/1000 # conversion en grammes + if not( 0<=donnee 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 - renvoie "" si ne marche pas""" - liste = chaine.split("-") + renvoie "" si ne marche pas. + Si jamais la date est au format avec des / ça devrait passer aussi.""" + if "/" in chaine: + liste = chaine.split("/") + else: + liste = chaine.split("-") if len(liste) != 3: 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 == "": return "" else: - return (str(date.year)+"-"+str(date.month)+"-"+str(date.day)) - + #return (str(date.year)+"-"+str(date.month)+"-"+str(date.day)) + return str(date) def delta_date(date1,datenaissance): """ renvoie le nombre de jours (entier) entre date1 et datenaissance format "datetime" @@ -136,141 +163,238 @@ 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, court=False): + """ prend en argument le dictionnaire de requête et renvoie la config, et les + tableaux de données + court : si True est précisé, on ne met que le nom dans la config (enfant + additionnel)""" + + # Régler la configuration + config = gere_configuration(data,liste_err, court) + + # 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 veut extrapoler au-delà du jour maxi, on adapte + + # Si on a choisi la même échelle de données + if config.get("memechelle") == "oui": + config["non_sauve"]["maxi"] = calcule_max_graphique([j for lj in listes_jours.values() for j in lj]) + # En cas d'extrapolation, on prend le maxi + if config["non_sauve"]["calculextradata_type"] !="" and config["non_sauve"]["calculextradata_age"]>config["non_sauve"]["maxi"]: + config["non_sauve"]["maxi"] = int(config["non_sauve"]["calculextradata_age"]) +1 + 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_configuration(data,liste_err): +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_symbole(chaine): + """ prend en arg une chaîne genre "o", ">" et vérifie si c'est un symbole valide. + Renvoie ce symbole-là ou le défaut""" + if chaine in CONFIG["liste_symboles"]: + return chaine + else: + return DEFAUT["symbole"] + +def gere_configuration(data,liste_err, court=False): """ 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 = {} + Vérifie que chaque entrée est cohérente évidemment. + court : si mis à True, on ne met que le nom dans la configuraion, + ainsi que la date de naissance et le sexe""" + # 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[:CONFIG["longueur_max_nom_bebe"]] - + + naissance = data.get("naissance","") + if naissance !="": + naissance = convertit_date_vers_python(naissance,liste_err) + configuration["naissance"] = naissance + sexe = data.get("sexe","") if not (sexe in ["F","M","N"]): warning("Le sexe de l'enfant est invalide ! "+sexe,liste_err) sexe = "N" configuration["sexe"] = sexe + + if not(court): + + 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"]): + tyc = "P" + configuration["typecourbe"] = tyc + + # unité + unite = data.get("unite","") + if not (unite in CONFIG["liste_unites"]): + unite = "" + #warning("L'unité "+unite+" n'est pas reconnue !",liste_err) + configuration["unite"] = unite - naissance = data.get("naissance","") - if naissance !="": - naissance = convertit_date_vers_python(naissance,liste_err) - configuration["naissance"] = naissance + # grille + configuration["grille"] = gere_checkbox(data.get("grille","")) - # Type de courbe. Au pire on met P - tyc = data.get("typecourbe","") - if not (tyc in ["P","Z"]): - tyc = "P" - configuration["typecourbe"] = tyc + # 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","")) - # unité - unite = data.get("unite","") - - 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" - # maxi. 0 signifie qu'on veut pas de maxi - maxi = data.get("maxi","") - if maxi == "": - configuration["maxi"] = 0 - else: - configuration["maxi"] = int(convertit_jours_vers_python(maxi,liste_err)) - - # dimensions du graphique - largeur = data.get("largeur","") - if largeur == "": - largeur = DEFAUT["largeur_graphique"] - else: - try: - largeur = int(largeur) - except: - warning("La largeur "+largeur+"est invalide !",liste_err) + # maxi. 0 signifie qu'on veut pas de maxi + maxi = data.get("maxi","") + if maxi == "": + configuration["maxi"] = 0 + else: + configuration["maxi"] = int(convertit_jours_vers_python(maxi,liste_err)) + + # dimensions du graphique + largeur = data.get("largeur","") + if largeur == "": 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 = DEFAUT["hauteur_graphique"] - else: - try: - hauteur = int(hauteur) - except: - warning("La hauteur "+hauteur+"est invalide !",liste_err) + else: + try: + largeur = int(largeur) + except: + warning("La largeur "+largeur+"est invalide !",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 = 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 + else: + try: + hauteur = int(hauteur) + except: + warning("La hauteur "+hauteur+"est invalide !",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 + 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 + + configuration["couleurs"] = {} + # gérer les couleurs + for clecouleur in DEFAUT["couleurs"]: + coul = rgb_vers_tuple(data.get("couleur_"+clecouleur,""),DEFAUT["couleurs"].get(clecouleur, ""),liste_err) + configuration["couleurs"][clecouleur] = coul + + # symbole + configuration["symbole"] = gere_symbole( data.get("symbole", "")) - # 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["non_sauve"]["grilleamelio"] = gere_checkbox(data.get("grilleamelio","")) - positionlegende = data.get("positionlegende","") - if not(positionlegende in ['upper left','upper right','lower left','lower right']): - positionlegende = "upper left" - configuration["positionlegende"] = positionlegende - - 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 + #### 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"] = "" + + # Tracer les calculs sur la grille + configuration["non_sauve"]["calculextradata_trace"] = gere_checkbox(data.get("calculextradata_trace")) + configuration["non_sauve"]["calculextratemps_trace"] = gere_checkbox(data.get("calculextratemps_trace")) + return configuration - -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""" - # On construit la liste des couples + +## 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 "age_"+str(i) in data.keys(): + if data.get(typedonnee+"_"+str(i), "") != "": # si la donne de ce type existe + 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) @@ -279,7 +403,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 @@ -287,37 +411,97 @@ 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] + l_donnee = [x[1] for x in liste_donnees] - return (l_jours,l_poids) - + 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 = copy.deepcopy(config) + gros_dico["version"] = CONFIG["version"] 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"]) + # Calcul de toutes les dates de données + l_dates_poids = [convertit_date_vers_texte( config["naissance"] + datetime.timedelta(days=jours) ) for jours in l_jours] + l_dates_taille = [convertit_date_vers_texte( config["naissance"] + datetime.timedelta(days=jours) ) for jours in l_jourst] + gros_dico["data_dates_poids"]= l_dates_poids + gros_dico["data_dates_taille"] = l_dates_taille + + # gérer l'age maxi gros_dico["maxi"] = convertit_age_vers_texte(gros_dico["maxi"]) # gérer les couleurs -# for cle in ["couleur1", "couleur2", "couleur3", "couleur_fond","couleur_grille","couleur_cadretxt"]: -# gros_dico[cle] = tuple_vers_rgb(gros_dico[cle]) 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 fusionne_donnees(listes_jours,listes_donnees, listes_dates): + """ 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 + Le troisième paquet de listes (les dates) peut être vide ou bien simiaire : même clés.""" + + 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 """ + """ 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) @@ -326,20 +510,48 @@ def fichier_json_vers_configdonnees(chaine,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 = {} + listes_dates = {} + 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",[]) + listes_dates[typed] = valform.get("data_dates_"+typed,[]) + else: + listes_jours[typed] = valform.get("data_jours_"+typed,[]) + listes_donnees[typed] = valform.get("data_"+typed,[]) + listes_dates[typed] = valform.get("data_dates_"+typed,[]) + + + debug("Avant fusion : listes jours "+str(listes_jours),liste_err) + liste_donnees = fusionne_donnees(listes_jours,listes_donnees, listes_dates) + 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,DEFAUT["nb_data"]) + valform["nb_data"] = max(len(liste_donnees) +2,DEFAUT["nb_data"]) return valform - \ No newline at end of file + +#### Pour l'insertion d'une 2e (ou plus) courbe sur le graphique, ue fonction qui sépare tout ça +def eclate_donnees_additionnelles(conf, ljours, ldonnees, symb, couleur): + """ conf est la config (on ne garde que le nom) pour un enfant additionnel, + ljours et ldonnees les dictionnaires de listes contenant les données. + symb est le symbole choisi pour cette courbe additionnelle (déjà vérifié) + On fabrique un joli dictionnaire typed -> (conf lj, ldonnee) avec le nom de l'enfant, + et les données pour chaque typed""" + print("test conf avant "+str(ldonnees)+str(ljours)) + + retour = {} + conf["symbole"] = symb # On ajoute le symbole additionnel + conf["couleurcourbe"] = couleur # la couleur + for typed in CONFIG["liste_typedonnees"]: + retour[typed] = (conf, ljours[typed], ldonnees[typed]) + + print("test "+str(retour)) + return retour \ No newline at end of file