Changement de format pour l'export, ajout des boutons qui vont avec + divers
authorDenise sur Lya <sekhmet@lya>
Thu, 18 Jun 2020 12:40:06 +0000 (14:40 +0200)
committerDenise sur Lya <sekhmet@lya>
Thu, 18 Jun 2020 12:40:06 +0000 (14:40 +0200)
app.py
data/FAQ_data.txt
data/changelog_data.txt
gestion_donnees.py
static/outilspage.js
static/requetes.js
static/style.css
templates/base.html
templates/changelog.html
templates/faq.html
templates/index.html

diff --git a/app.py b/app.py
index 7baef86c03fd113bba7850ff607ac854ebe63699..0d2054bbc9cc4a825b2525fa0e9d078b95a190ab 100644 (file)
--- a/app.py
+++ b/app.py
@@ -27,8 +27,8 @@ def index():
             ## charger les donneés dans le formulaire
             fichier = flask.request.files['fichier_donnees']
         
-            val_form = fichier_texte_vers_configdonnees(fichier,liste_err)
-        
+            val_form = fichier_json_vers_configdonnees(fichier,liste_err)
+            #return str(val_form['nb_data'])
     return flask.render_template("index.html",err=liste_err[1],valform=val_form)
 
 @app.route('/apropos')
@@ -46,9 +46,10 @@ def courbe_image(ext):
     config = gere_configuration(data,liste_err)    
     l_jours,l_poids = gere_donneespoids(data,config["naissance"],liste_err)
 
-    # préparer l'export
-    texte = configuration_vers_texte(config)
-    texte += donnees_poids_vers_texte(l_jours,l_poids)  
+    texte = donnees_vers_json(l_jours,l_poids,config)
+    
+    # noter le nom de l'enfant pour l'export
+    nomenfant = simplifie_nom(config['nom'])
 
     # créer la figure
     try:
@@ -67,12 +68,14 @@ def courbe_image(ext):
             reponse = flask.jsonify({ "result":result, 
                 "messages": liste_err[1], 
                 "image": base64.b64encode(output.getvalue()).decode("ascii"),
-                "export_txt": texte})
+                "export_txt": texte,
+                "nomenfant": nomenfant})
         else:
             reponse = flask.jsonify({ "result":result, 
                 "messages": liste_err[1]+liste_err[0], 
                 "image": "",
-                "export_txt": texte})
+                "export_txt": texte,
+                "nomenfant": nomenfant})
         return reponse
         #return flask.Response(base64.b64encode(output.getvalue()), mimetype='text/plain')
     elif ext == "png" and result == "success":
@@ -84,19 +87,21 @@ def courbe_image(ext):
     
     #return flask.Response(base64.b64encode(output.getvalue()), mimetype='text/plain')
 
-@app.route("/export_donnees",methods=['POST'])
-def export_donnees():
-    # exporte les données au format texte
-    liste_err = initialise_erreurs()
-    data = flask.request.form
-    
-    config = gere_configuration(data,liste_err)
-    l_jours,l_poids = gere_donneespoids(data,config["naissance"],liste_err)
-
-    texte = configuration_vers_texte(config)
-    texte += donnees_poids_vers_texte(l_jours,l_poids)    
-    
-    return flask.Response(texte,mimetype="text/plain")
+#@app.route("/export_donnees",methods=['POST'])
+#def export_donnees():
+#    # exporte les données au format texte
+#    liste_err = initialise_erreurs()
+#    data = flask.request.form
+#    
+#    config = gere_configuration(data,liste_err)
+#    l_jours,l_poids = gere_donneespoids(data,config["naissance"],liste_err)
+#
+#    texte = configuration_vers_texte(config)
+#    texte += donnees_poids_vers_texte(l_jours,l_poids)    
+#    
+#    #texte = donnees_vers_json(l_jours,l_poids,config)
+#    #return flask.Response(texte,mimetype="application/json")
+#    return flask.Response(texte,mimetype="text/plain")
 
 @app.route("/faq")
 def faq():
index fe2f5a5633ef2ff641474c1bee68bf69a77a9396..2313fa8f2c1568f186ff92144d247dc73d4e4dda 100644 (file)
@@ -1,10 +1,12 @@
-"Utilisation","Comment tracer la courbe ?","Remplissez le formulaire (vous pouvez laisser par défaut tous les champs qui ne vous «&nbsp;parlent&nbsp;» pas), puis cliquez sur ""Je veux la courbe !"". La courbe s'affiche plus bas. Si vous modifiez les données du formulaire, vous pouvez re-cliquer sur ce bouton pour la mettre à jour. Et c'est tout !"
+"Utilisation","Comment tracer la courbe ?","Remplissez le formulaire (vous pouvez laisser par défaut tous les champs qui ne vous «&nbsp;parlent&nbsp;» pas), puis cliquez sur ""Je veux la courbe !"". La courbe s'affiche plus bas. Si vous modifiez les données du formulaire, vous pouvez re-cliquer sur ce bouton pour la mettre à jour. Et c'est tout ! Vous pouvez également télécharger la courbe en cliquant sur le bouton juste en dessous."
 
-"Utilisation","Comment sauvegarder mes données pour une prochaine fois ?","L'outil ne propose pas de sauvegarde en ligne, car il ne garde aucune donnée personnelle. Quand vous demandez à tracer la courbe, il s'affiche en bas un champ de texte. Si vous copiez-collez ce contenu et le gardez dans un fichier, vous n'aurez pas à re-saisir les données. Utilisez la partie ""Importer le fichier de données"" puis ""Charger les données"" : les champs seront alors pré-remplis et vous pourrez les modifier ou ajouter des données (par exemple une pesée récente)."
+"Utilisation","Comment sauvegarder mes données pour une prochaine fois ?","L'outil ne propose pas de sauvegarde en ligne, car il ne garde aucune donnée personnelle. Vous pouvez télécharger vos propres données (dans un format adapté) en cliquant sur «&nbsp;Télécharger les données&nbsp;». Enregistrez ce fichier quelque part. Pour les réutiliser, utilisez la partie ""Importer le fichier de données"" puis ""Charger les données"" : les champs seront alors pré-remplis et vous pourrez les modifier ou ajouter des données (par exemple une pesée récente)."
 
 "Utilisation","Je ne comprends pas comment saisir les données âge/poids !","Pour chaque pesée, vous avez le choix entre donner son âge, ou donner la date de la pesée. Pour la date de la pesée, il faut que vous ayiez rempli la date de naissance (sinon l'outil ne peut pas calculer l'âge !). Pour l'âge, vous pouvez indiquer en années, mois, semaines, jours, et mélanger plusieurs unités. Par exemple 2m3j pour «&nbsp;2 mois 3 jours&nbsp;». Ne remplissez qu'un des deux champs : âge ou date. Puis saisissez le poids en kilogrammes dans la colonne de droite. <br>
 Si vous manquez de lignes, vous pouvez cliquer sur ""Ajouter des lignes"" pour avoir plus de champs de données. Il n'y a pas de problème à laisser des champs vides, ils seront simplement ignorés."
 
+"Utilisation","Une erreur s'affiche en bas de ma courbe, je ne comprends pas.","Si le message d'erreur n'est pas clair pour vous, c'est qu'il est probablement dû à un souci interne. Si vous le pouvez, contactez l'administratrice, en citant le message d'erreur et en donnant les données qui ont amené à cette erreur (fichier de données, ou données saisies). Merci d'avance ! :)"
+
 "Courbe","À quoi sert ce site ?","Il sert à tracer la courbe de poids des bébés et jeunes enfants en fonction de leur âge, comme sur le carnet de santé, et la compare aux courbes de références de l'Organisation Mondiale de la Santé."
 
 "Courbe","Pourquoi des courbes OMS et pas des courbes françaises ?","Les particularités de ces données sont les suivantes : bébés et enfants choisis dans différents pays du monde (et non dans un seul pays), dans des familles raisonnablement aisées (pour éviter les problèmes liés à la malnutrition), et des bébés allaités. Plus d'infos <a href=""https://www.who.int/childgrowth/standards/technical_report/en/"">ici</a> (en anglais). L'idée générale était de donner une «&nbsp;référence&nbsp;» de croissance de l'enfant de l'espèce humaine. Cela ne veut pas dire que les courbes du carnet de santé français sont mauvaises (si vous avez celles datant d'avant 2018, elles sont un peu anciennes par contre), de fait, elles sont très peu différentes. Pour les autres pays je ne sais pas, je n'ai pas comparé."
@@ -16,10 +18,21 @@ Si vous manquez de lignes, vous pouvez cliquer sur ""Ajouter des lignes"" pour a
 "Courbe","Pourquoi faire des courbes de référence différenciées selon le sexe ?","Il se trouve que l'évolution de poids diffère selon ce critère. S'il s'agissait d'une simple différence de poids en moyenne, une courbe mixte serait adaptée (puisqu'on s'intéresse à l'allure de la courbe et non au poids absolu), mais les courbes sont légèrement différentes.<br>
 Vous trouverez <a href=""static/courbe_oms_gf_1an.png"">ici</a> et <a href=""static/courbe_oms_gf_5ans.png"">là</a> deux graphiques où on voit les courbes de poids garçons et filles superposées."
 
-"Courbe","Pourquoi une courbe de «&nbsp;neutre&nbsp;» ? Et c'est quoi ?","Pourquoi pas ? C'est simplement une courbe moyennée des courbes garçon/fille. Ce n'est absolument pas une référence réelle, mais ça ne coûtait rien d'essayer. Je n'ai aucune connaissance dans les données de croissance des enfants intersexes, et s'il existe pour ces enfants des courbes de référence, peut-être que celle-ci aurait du sens, peut-être pas. Elle permet accessoirement de générer une courbe «&nbsp;acceptable&nbsp;» même si on n'a pas spécifié sexe masculin/féminin. Au pire vous pouvez considérer que c'est une «&nbsp;courbe jouet&nbsp;»."
+"Courbe","Pourquoi une courbe de «&nbsp;neutre&nbsp;» ? Et c'est quoi ?","Pourquoi pas ? C'est simplement une courbe moyennée des courbes garçon/fille. Ce n'est absolument pas une vraie référence, mais ça ne coûtait rien d'essayer. Je n'ai aucune connaissance dans les données de croissance des enfants intersexes, et s'il existe pour ces enfants des courbes de référence, peut-être que celle-ci aurait du sens, peut-être pas. Elle permet accessoirement de générer une courbe «&nbsp;acceptable&nbsp;» même si on n'a pas spécifié sexe masculin/féminin. Au pire vous pouvez considérer que c'est une «&nbsp;courbe jouet&nbsp;»."
+
+"Courbe","C'est quoi la différence entre la courbe percentiles et moyenne/écarts-types ?","Ce sont deux manières de comparer son enfant à la «&nbsp;référence&nbsp;». Si vous ne savez pas la différence entre une moyenne et une médiane, retenez juste que ce sont des calculs un peu différents, mais cela ne change pas l'allure de la courbe, si votre enfant «&nbsp;suit&nbsp;» un couloir sur un type de courbe ce sera pareil sur l'autre. Vous pouvez au pire choisir celle que vous préférez. ;)
+<br>Pour les curieuses et les curieux :
+<ul>
+<li>Médiane et percentiles : si votre enfant est (par exemple) au 30e percentile, cela signifie que 29% des enfants sont plus petits que lui, et 31% sont plus grands. Cela donne une idée de où se situe l'enfant par rapport aux autres. <a href=""https://fr.wikipedia.org/wiki/Centile"">Plus d'infos (maths)</a></li>
+<li>Moyenne et écart-types : la moyenne est obtenue en faisant la somme et en divisant par le nombre d'enfants. L'écart-type (noté &sigma; ou z) est une sorte d'«&nbsp;écart moyen&nbsp;» à la moyenne. On regarde donc généralement la moyenne, la moyenne + 1&sigma;, moyenne +2&sigma;, moyenne -1 &sigma;, etc. <a href=""https://fr.wikipedia.org/wiki/%C3%89cart_type"">Plus d'infos (maths)</a></li>
+</ul>
+Si la répartition des données est «&nbsp;bonne&nbsp;» (on parle de gaussienne entre matheuses et matheux), alors la moyenne correspond à la médiane, la moyenne + 1&sigma; correspond environ au 84e percentile, la moyenne -1&sigma; correspond au 16e percentile. Pour ces données, il semble que ce soit assez proche d'une gaussienne."
 
 "Divers","Pourquoi cet outil ?","Tout a commencé sur le <a href=""https://forum.lllfrance.org/"">forum de La Leche League</a>, où de nombreuses mamans (et parfois quelques papas) viennent poser des questions sur l'allaitement. Souvent, se pose la question de la prise de poids du bébé, qui est un bon indicateur de si le bébé reçoit assez de lait. Le poids dans l'absolu étant peu pertinent, il est utile de «&nbsp;tracer&nbsp;» la courbe de poids. Il n'existe actuellement que peu d'outils qui permettent de le faire (l'OMS en fournit un qui ne fonctionne que sous windows), en voici un."
 
 "Divers","Mais qui es tu donc ?","Je suis une de ces nombreuses mamans qui s'est posée un jour (et un autre jour aussi) la question de la prise de poids de son enfant. Adepte de solutions libres et multi-plateforme, je n'ai rien trouvée qui me permette de tracer cette courbe, que ce soit pour mon enfant ou celui des autres. Je suis par ailleurs férue de programmation (et je l'enseigne d'ailleurs), et donc... voilà."
 
 "Technique","Quelle est la technologie utilisée derrière ?","Il s'agit de <a href=""https://flask.palletsprojects.com/en/1.1.x/"">flask</a>, un petit framework de développement web en <a href=""https://www.python.org/"">python</a>. Il y a une petite dose de <a href=""https://developer.mozilla.org/fr/docs/Web/JavaScript"">JavaScript</a> pour assaisonner le tout, et voilà."
+
+"Technique","J'ai trouvé un affreux bug !","N'hésitez pas à me contacter avec le plus de détails possibles à ce sujet. Par exemple quelles données, quelles manipulations vous ont amené.e à ce bug. Vous pouvez aussi préciser votre navigateur. Plus vous me fournissez d'informations, plus j'ai de chances de résoudre le bug !"
+
index e5b81a03973c66df8f6a4888688faa901705b39f..f9394445dc47f5f1bffa828d5891d0cd98059b0e 100644 (file)
@@ -1,3 +1,15 @@
+"Version 0.4","18/06/2020","<ul>
+<li>Mis certaines sections en masquées par défaut, on peut cliquer pour les afficher</li>
+<li>Encore des ajouts dans la FAQ</li>
+<li>Si je continue comme ça je vais arriver à une version 1.0 avant d'avoir fini !</li>
+</ul>"
+
+"Version 0.3","17/06/2020","<ul>
+<li>Le format d'export est maintenant du json (vos anciennes sauvegardes ne marchent plus, désolée !)</li>
+<li>Bouton de téléchargement des données et de la courbe, avec un nom sympa en plus («&nbsp;courbe_nomenfant.png&nbsp;» par exemple)</li>
+<li>Le texte ""explicite"" d'export est maintenant caché par défaut (on peut quand même l'avoir en cliquant dessus)</li>
+</ul>"
+
 "Version 0.2","16/06/2020","<ul>
 <li>Mise en place du changelog (sans blague)</li>
 <li>Courbe ""sexe neutre"" expérimentale</li>
@@ -6,7 +18,7 @@
 <li>Mise à jour de la FAQ</li>
 </ul>"
 
-"Version 0.1","14/06/2020","Mise en place du site web, en cours de développement.
+"Version 0.1","14/06/2020","<p>Mise en place du site web, en cours de développement.</p>
 <ul>
 <li>Tracé de la courbe</li>
 <li>Export/import des données</li>
index 71c96231c6c9ba6fe78d5bdc8f2736fc4b7ccbb1..a2d0a1e2b819431a83391243e9764a62613dd7ff 100644 (file)
@@ -4,13 +4,15 @@
 from configuration import *
 from gestion_erreurs import *
 import datetime
+import json
+import unidecode
 
 
 ############ Fonctions de conversion
 
 def convertit_jours_vers_python(chaine,liste_err):
     """ convertit une chaine de type 1a 3m 1s 10j en jours
-    Renvoie un nombre de jours en entiers.
+    Renvoie un nombre de jours en float
     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
     """
@@ -41,19 +43,19 @@ def convertit_jours_vers_python(chaine,liste_err):
                 warning("convertit_jour_vers_python : caractère invalide : "+lettre,liste_err)
     # à la fin s'il reste qqch on le garde dans les jours
     if chainenombre != "":
-        agejour += int(chainenombre)
+        agejours += int(chainenombre)
     if agejours<0:
         warning("L'âge est négatif !",liste_err)
         agejours = 0
-    return round(agejours)
+    return agejours
 
 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 - round(annees*jours_dans_annee)
+    restant = nombre - annees*jours_dans_annee
     mois = int(restant/jours_dans_mois)
-    jours= nombre - round(mois*jours_dans_mois + annees*jours_dans_annee)
+    jours= round(nombre - mois*jours_dans_mois - annees*jours_dans_annee)
     
     chaine = ""
     if annees >0:
@@ -64,6 +66,18 @@ def convertit_age_vers_texte(nombre):
         chaine += str(jours)+"j"
     return chaine
 ##########################
+    
+
+def simplifie_nom(chaine):
+    """ simplifie le nom chaine afin d'en faire une extension
+    pour le nom du fichier. Met tout en minuscules et vire les caractères spéciaux
+    et max 15 caractères"""
+    chaine2 = ""
+    for l in chaine:
+        if l.isalpha():
+            chaine2+=l
+    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.
@@ -82,9 +96,9 @@ def convertit_poids_vers_python(chaine,liste_err):
         poids = 0
     return poids
 
-def convertit_poids_vers_texte(poids):
-    """ convertit un poids vers du texte. Rien à dire là pour l'instant """
-    return str(poids)
+#def convertit_poids_vers_texte(poids):
+#    """ convertit un poids vers du texte. Rien à dire là pour l'instant """
+#    return str(poids)
 
 #########################
 
@@ -101,7 +115,7 @@ def convertit_date_vers_python(chaine,liste_err):
             date = datetime.date(int(liste[0]),int(liste[1]),int(liste[2]))
         except:
             date = ""
-            warning("Impossible de lire la date "+chaine)
+            warning("Impossible de lire la date "+chaine,liste_err)
         return date
     
 def convertit_date_vers_texte(date):
@@ -127,7 +141,7 @@ def delta_date(date1,datenaissance):
 
 
 def gere_configuration(data,liste_err):
-    """ prend en argument le dictionnaire de requête (configuratio imparfaite), et
+    """ 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 = {}
@@ -176,7 +190,7 @@ def gere_configuration(data,liste_err):
         configuration["maxi"] = convertit_jours_vers_python(maxi,liste_err)
     
     # dimensions du graphique
-    largeur = data.get("largeur")
+    largeur = data.get("largeur","")
     if largeur == "":
         largeur = largeur_graphique
     else:
@@ -193,7 +207,7 @@ def gere_configuration(data,liste_err):
             warning("Largeur trop petite !",liste_err)
     configuration["largeur"] = largeur
     
-    hauteur = data.get("hauteur")
+    hauteur = data.get("hauteur","")
     if hauteur == "":
         hauteur = hauteur_graphique
     else:
@@ -225,21 +239,19 @@ def gere_configuration(data,liste_err):
         positionlegende = "upper left"
     configuration["positionlegende"] = positionlegende
     
-    
-    
     return configuration   
 
-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 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):
@@ -279,46 +291,87 @@ def gere_donneespoids(data,naissance,liste_err):
     return (l_jours,l_poids)
     
     
-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"
+#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
+
+
+
+
+
+def donnees_vers_json(l_jours,l_poids,config):
+    """ retourne le json à renvoyer"""
+    gros_dico = config.copy()
+    l_jours2 = [convertit_age_vers_texte(d) for d in l_jours]
+    gros_dico["data_j"] = l_jours2
+    gros_dico["data_p"] = l_poids
+    # 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"])
         
-    texte+="\n"
-    return texte
+    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_texte_vers_configdonnees(fichier,liste_err):
-    """ prend le texte importé et l'exporte vers configuration et données
-    sous forme de valeurs du formulaire """
+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)
+    # 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]
+        
+    valform["nb_data"] = max(long +2,nombre_lignes_form)
     
-    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)
+    return valform
     
-    #le nb max du formulaire
-    valform["nb_data"] = indice_formulaire +2
     
-    return valform
+    
\ No newline at end of file
index de87963c401879276a7f090f5bffdfeb656122b9..8da9e88a05ce7f68ae3c0920ea02bc8ffab7033c 100644 (file)
@@ -23,3 +23,29 @@ function ajoutelignes()
        }
        
 }
+
+// Affichage de la textarea "export"
+function affiche_export()
+{
+       document.getElementById("export").style.display = "block" ;
+       
+}
+
+function affiche_cache(id,elemcourant)
+{
+       // affiche et/ou cache l'élément id, tout en changeant le this
+       // en afficher/masquer
+       elem = document.getElementById(id)
+       if(elem.style.display == "block")
+       {
+               elem.style.display = "none";
+               elemcourant.innerHTML = "Afficher" ;
+       }
+       else
+       {
+               elem.style.display = "block" ;
+               elemcourant.innerHTML = "Masquer" ;
+               
+       }
+       
+}
index 072e88044e7117a45820a636bfc8e284f0b83427..b7e719abc713911494c624962df82ed33cd01c79 100644 (file)
@@ -9,21 +9,34 @@ function appelle_image()
        requete.onreadystatechange = function()
        {
                if (this.readyState == 4 && this.status == 200) {
+                       // On nettoie
+                       nettoie_erreurs()
+                                               
                        // on récupère les différents champs de la réponse
                        var result = this.response.result ;
                        var image = this.response.image ;
                        var liste_warnings = this.response.messages
                        var texte = this.response.export_txt;
+                       var nomenfant = this.response.nomenfant ;
+
                        // on affiche l'export des données
                        document.getElementById('export').innerHTML = texte;
                        document.getElementById('sectionexport').style.display = "block";
+               
+                       
+                       var boutondl = document.getElementById("export_dl") ;
+                       boutondl.setAttribute('onclick',"download_file('donnees_"+nomenfant+".json', 'application/json;charset=utf-8','"+encodeURIComponent(texte) +"')")
+               
 
                        if(result == "success")
-                       {
+                       {       
                                // On affiche l'image
                                document.getElementById('courbe').src = 'data:image/png;base64,'+(image);
                                document.getElementById('sectioncourbe').style.display = "block";
-                               
+
+                               //document.getElementById("courbe_dl").setAttribute('href', 'data:image/png;base64,' + image);
+                               boutondl = document.getElementById("courbe_dl") ;
+                               boutondl.setAttribute('onclick',"download_file('courbe_"+nomenfant+".png', 'image/png;base64','"+image +"')")
 
                                // Si y'a eu des warnings, faut les afficher
                                if(liste_warnings.length != 0)
@@ -41,7 +54,7 @@ function appelle_image()
                        }
                        else{ // si la génération de l'image a merdé
                                
-                               // afficher la liste des warnings
+                               // afficher la liste des erreurs
                                var elem_div = document.getElementById('courbe_erreurs') ;
                                elem_div.style.display = "block" ;
                                var ul = elem_div.children[1] ;
@@ -58,3 +71,35 @@ function appelle_image()
        requete.open("POST","courbe/b64",true)
        requete.send(formData)
 }
+
+function nettoie_erreurs()
+{
+       // fonction qui nettoie les erreurs affichées sur la page
+       // vider les warnings
+       var elem_div = document.getElementById('courbe_warnings') ;
+       elem_div.style.display = "none" ;
+       var ul = elem_div.children[1] ;
+       ul.innerHTML = "";
+       
+       // vider les erreurs
+       elem_div = document.getElementById('courbe_erreurs') ;
+       elem_div.style.display = "none" ;
+       ul = elem_div.children[1] ;
+       ul.innerHTML = "" ;
+       
+}
+
+
+function download_file(filename,mimetype,data) {
+  var element = document.createElement('a');
+  element.setAttribute('href', 'data:'+mimetype+',' + data);
+  element.setAttribute('download', filename);
+
+  element.style.display = 'none';
+  document.body.appendChild(element);
+
+  element.click();
+
+  document.body.removeChild(element);  
+       
+}
index 3113832076741ee02f842c5967d247a56f38e3e9..58fdd2418fd554889d8ac4de0b224d7f0a49ad27 100644 (file)
@@ -13,6 +13,7 @@ body {
 #export {
        width: 25em;
        height: 20em;
+       display:none;
 }
 
 #courbe_warnings {
@@ -22,3 +23,15 @@ body {
 #courbe_erreurs {
        display: none;
 }
+
+#import_donnees, #pref_graphique{
+       display: none;
+}
+
+.bouton {
+       text-decoration: underline;
+}
+
+.bouton:hover {
+       cursor:pointer;
+}
index 9fa5263c91c7e3464bd6678aad18dc21e7b34fea..694266c9b1b67800329034b84411746c2cfb1ccf 100644 (file)
@@ -33,7 +33,7 @@
                
                <nav><a href="/">Accueil et saisie des données</a> |
                <a href="/apropos">À propos</a> |
-               <a href="/faq">Foire Aux Questions</a> |
+               <a href="/faq">FAQ</a> |
                <a href="/changelog">Changelog</a>
                </nav> 
                
index 24447f69cd1ae159351dfdfbb383d0869f5dbab4..7ba96cd7a53767ae5357ba55f5dcc1c2609e9885 100644 (file)
@@ -7,7 +7,8 @@
 <h3>{{ ligne[0] }}</h3>
 <div class="date">Le {{ ligne[1] }}</div>
 
-<p>{{ ligne[2]|safe }}</p>
+<div class="contenu_changelog">
+{{ ligne[2]|safe }}
 </div>
 {% endfor %}
 
index 301f5d757d69f0dd11714f2982c67177cb0e1e22..162f68dbb76b365f2f530ceff16fbeeb0efd698d 100644 (file)
@@ -1,6 +1,6 @@
 {% extends "base.html" %}
 {% block contenu %}
-<h2>Foire Aux Questions</h2>
+<h2>Foire Aux Questions (FAQ)</h2>
 
 <div id="sommaire">
 <ul>{% for cat in lcateg %}
index 23a9d368ee9ca89ebab82fc134263d28b8634462..60693d40f8b68fc7bef171135f83a8fdd8e8f173 100644 (file)
@@ -4,57 +4,42 @@
 <hr>
 
 
-<h2>Données du graphique</h2>
+<h2>Données de l'enfant</h2>
 
+<h3>Importer un fichier </h3>
+<div class="bouton" onclick="affiche_cache('import_donnees',this)">Afficher</div>
+<div id="import_donnees">
  <form action="/" method="post" enctype="multipart/form-data">
+
+
+
   <label for="fichier_donnees">Importer le fichier de données</label>
   <input type="file" name="fichier_donnees">
   <input type="submit" name="valider_fichier" value="Charger les données">
 </form> 
-
+</div>
+<hr>
 
 <form id="donnees_enfant">
        
-
+<div>   
+<input type="reset" value="Effacer les données du formulaire">         
+</div>
        
 <h3>Informations sur l'enfant</h3>
 <ul>
-<li><label>Nom de l'enfant :</label> <input type="text" name="nom" value="{{ valform.nom }}"></li>
-<li><label>Sexe :</label> <input type="radio" name="sexe" value="F" {%if valform.sexe == "F" %}checked{% endif %}> féminin 
+<li><label>Nom de l'enfant&nbsp;:</label> <input type="text" name="nom" value="{{ valform.nom }}"></li>
+<li><label>Sexe&nbsp;:</label> <input type="radio" name="sexe" value="F" {%if valform.sexe == "F" %}checked{% endif %}> féminin 
 | <input type="radio" name="sexe" value="M" {%if valform.sexe == "M"%} checked {% endif %}> masculin | <input type="radio" name="sexe" value="N" {%if valform.sexe == "N"%} checked {% endif %}> neutre (expérimental)</li>
-<li><label>Date de naissance : </label> <input type="date" name="naissance" value="{{ valform.naissance }}"></li>
+<li><label>Date de naissance&nbsp;: </label> <input type="date" name="naissance" value="{{ valform.naissance }}"></li>
 </ul>
 
 
-<h3>Préférences du graphique</h3>
-<ul>
-       <li><label>Type de courbe :</label> <input type="radio" name="typecourbe" value="P"
-       {% if valform.typecourbe == "P" or valform.typecourbe is not defined %} checked {% endif %}> Percentiles | <input type="radio" name="typecourbe" value="Z"
-       {% if valform.typecourbe == "Z" %}  checked {% endif %}> Moyenne et écarts-type</li>
-       <li><label>Grille : </label><input type="checkbox" name="grille" {%if valform.grille == "oui" or valform.grille is not defined %} checked {% endif %}></li>
-       <li><label>Unité : </label>
-               <select name="unite">
-                       <option value="" {% if valform.typecourbe == "" or valform is not defined %} selected {%endif %}>Par défaut</option>
-       {% for unite in ['jours','semaines','mois','années'] %}
-                       <option value="{{ unite }}"{% if valform.unite == unite %} selected {% endif %} >{{ unite }}</option>
-       {% endfor %}
-               </select></li>
-       <li><label>valeur maximum du graphique (facultatif, syntaxe similaire à l'âge, voir plus bas) </label><input type="text" name="maxi" value="{{ valform.maxi }}"></li>
-       <li>Dimensions du graphique : <label>largeur : </label><input type="text" name="largeur" value="{{ valform.largeur }}"> 
-       <label>hauteur : </label><input type="text" name="hauteur" value="{{ valform.hauteur }}"></li>
-       <li><label>Légende : </label><input type="checkbox" name="legende" {% if valform.legende == 'oui' %} checked{% endif %}> 
-Position : <select name="positionlegende">
-{%for (pos,posnom) in [('upper left','Haut gauche'),('upper right','Haut Droite'),('lower left','Bas gauche'),('lower right','Bas droite')] %}
-<option value="{{ pos }}">{{ posnom }}</option>
-{% endfor %}
-</select>
-       </li>
-</ul>
 
 
 
 <h3>Données de poids</h3> 
-<p>Syntaxe pour l'âge : utiliser j, s, m, a comme des "unités" (jours, semaines, mois, années). Vous pouvez mixer les unités, par exemple "3a2m5j" pour 3 ans, 2 mois et 5 jours. Les espaces sont ignorées, et il faut saisir des nombres entiers.</p>
+<p>Syntaxe pour l'âge&nbsp;: utiliser j, s, m, a comme des "unités" (jours, semaines, mois, années). Vous pouvez mixer les unités, par exemple "3a2m5j" pour 3 ans, 2 mois et 5 jours. Les espaces sont ignorées, et il faut saisir des nombres entiers.</p>
 <p>Saisir le poids en kilogrammes (par exemple "2.62" ou "2,62" pour 2 kilogrammes et 620 grammes).</p>
 <p>Il faut saisir la date ou l'âge. Si les deux sont saisis, seul l'âge comptera.</p>
 
@@ -72,35 +57,70 @@ Position : <select name="positionlegende">
 {% endfor %}
 
 </table>
+<p class="bouton" onclick="ajoutelignes()">Cliquer ici pour ajouter des lignes</p>
+
+
+
+<h3>Préférences du graphique</h3>
+<div class="bouton" onclick="affiche_cache('pref_graphique',this)">Afficher</div>
+
+<ul id="pref_graphique">
+       <li><label>Type de courbe&nbsp;:</label> <input type="radio" name="typecourbe" value="P"
+       {% if valform.typecourbe == "P" or valform.typecourbe is not defined %} checked {% endif %}> Percentiles | <input type="radio" name="typecourbe" value="Z"
+       {% if valform.typecourbe == "Z" %}  checked {% endif %}> Moyenne et écarts-type</li>
+       <li><label>Grille&nbsp;: </label><input type="checkbox" name="grille" {%if valform.grille == "oui" or valform.grille is not defined %} checked {% endif %}></li>
+       <li><label>Unité&nbsp;: </label>
+               <select name="unite">
+                       <option value="" {% if valform.typecourbe == "" or valform is not defined %} selected {%endif %}>Par défaut</option>
+       {% for unite in ['jours','semaines','mois','années'] %}
+                       <option value="{{ unite }}"{% if valform.unite == unite %} selected {% endif %} >{{ unite }}</option>
+       {% endfor %}
+               </select></li>
+       <li><label>valeur maximum du graphique (facultatif, syntaxe similaire à l'âge, voir plus bas) </label><input type="text" name="maxi" value="{{ valform.maxi }}"></li>
+       <li>Dimensions du graphique&nbsp;: <label>largeur&nbsp;: </label><input type="text" name="largeur" value="{{ valform.largeur }}"> 
+       <label>hauteur&nbsp;: </label><input type="text" name="hauteur" value="{{ valform.hauteur }}"></li>
+       <li><label>Légende&nbsp;: </label><input type="checkbox" name="legende" {% if valform.legende == 'oui' %} checked{% endif %}> 
+Position&nbsp;: <select name="positionlegende">
+{%for (pos,posnom) in [('upper left','Haut gauche'),('upper right','Haut Droite'),('lower left','Bas gauche'),('lower right','Bas droite')] %}
+<option value="{{ pos }}">{{ posnom }}</option>
+{% endfor %}
+</select>
+       </li>
+</ul>
+
+
 </form>
-<button onclick="ajoutelignes()">Ajouter des lignes</button>
 
 <hr>
 
 <button onclick="appelle_image()">Je veux la courbe !</button>
 
-
-<div id="sectioncourbe">
 <hr>
-<h2>Courbe</h2>
-<img id="courbe">
+<div id="sectioncourbe">
+
+       <h2>Courbe</h2>
+       <img id="courbe">
+       <div>
+       <button id="courbe_dl">Télécharger la courbe</button>
+       </div>
 </div>
 <div id="courbe_warnings">
-<p><strong>Alerte :</strong> la courbe a eu quelques soucis à se générer. Voici la liste des erreurs.</p>
-<ul></ul>
+       <p><strong>Alerte&nbsp;:</strong> la courbe a eu quelques soucis à se générer. Voici la liste des erreurs.</p>
+       <ul></ul>
 </div>
 <div id="courbe_erreurs">
-<p><strong>Alerte :</strong> La courbe n'a pas pu être générée. Vérifiez les données saisies, ou contactez l'administratrice. Erreurs : </p>
-<ul></ul>
+       <p><strong>Alerte&nbsp;:</strong> La courbe n'a pas pu être générée. Vérifiez les données saisies, ou contactez l'administratrice. Erreurs&nbsp;: </p>
+       <ul></ul>
 </div>
 
 <div id="sectionexport">
-<h2>Export des données</h2>
-<p>Vous trouverez ci-dessous les données exportées au format texte. Il peut être utile de les copier/coller
-quelque part et de les sauvegarder, comme ça la prochaine fois vous n'aurez pas à ressaisir tout à la main.</p>
+       <h2>Export des données</h2>
+       <p>Vous pouvez télécharger les données afin de ne pas avoir à les re-saisir la prochaine fois.</p>
 
-<textarea readonly id="export">
-</textarea>
+       <div><button id="export_dl">Télécharger les données</button></div>
+       <p>Si vous n'arrivez pas à télécharger les données, <a href='#export' onclick="affiche_export()">cliquez ici</a> pour les voir en texte clair&nbsp;: il vous suffira de les copier/coller dans un fichier texte.</p>
+       <textarea readonly id="export">
+       </textarea>
 
 </div>