feat: export/import paramètres par email + script merge côté serveur

GUI :
- Bouton "Exporter pour envoi" → fichier JSON sur le Bureau avec
  whitelist + blacklist + version + date, prêt à envoyer par email
- Bouton "Importer" → charge un JSON et fusionne (sans doublons)

Serveur :
- scripts/merge_params.py : fusionne les JSON reçus des établissements
  dans la config maîtresse dictionnaires.yml
  Usage : python scripts/merge_params.py export1.json export2.json

Workflow :
1. L'établissement ajuste les paramètres dans la GUI
2. Clique "Exporter" → fichier JSON
3. Envoie par email
4. On fusionne avec merge_params.py
5. On reconstruit l'exe avec la config enrichie

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-30 17:58:47 +02:00
parent 106f1fcd2e
commit 898ad9d82d
2 changed files with 230 additions and 3 deletions

View File

@@ -546,15 +546,34 @@ class App:
color_tag="#fce4ec",
)
# Bouton sauvegarder
# Boutons sauvegarder + exporter
btn_row = tk.Frame(self._params_frame, bg=CLR_BG)
btn_row.pack(fill=tk.X, pady=(4, 4))
export_btn = tk.Button(
btn_row, text="\u2709 Exporter pour envoi",
font=self._f_small, bg="#e3f2fd", fg="#1565c0",
relief=tk.GROOVE, cursor="hand2", padx=10, pady=4,
command=self._export_params,
)
export_btn.pack(side=tk.LEFT)
import_btn = tk.Button(
btn_row, text="\u2B07 Importer",
font=self._f_small, bg="#fff3e0", fg="#e65100",
relief=tk.GROOVE, cursor="hand2", padx=10, pady=4,
command=self._import_params,
)
import_btn.pack(side=tk.LEFT, padx=(4, 0))
save_btn = tk.Button(
self._params_frame, text="Sauvegarder les paramètres",
btn_row, text="Sauvegarder",
font=self._f_small, bg=CLR_PRIMARY, fg="white",
activebackground="#1d4ed8", activeforeground="white",
relief=tk.FLAT, cursor="hand2", padx=12, pady=4,
command=self._save_params,
)
save_btn.pack(anchor="e", pady=(4, 4))
save_btn.pack(side=tk.RIGHT)
# Charger les valeurs initiales depuis la config
self._load_params()
@@ -1198,6 +1217,99 @@ class App:
except Exception:
pass
def _export_params(self):
"""Exporte les paramètres whitelist/blacklist dans un fichier JSON pour envoi par email."""
try:
import json as _json
from datetime import datetime
wl = list(self._wl_listbox.get(0, tk.END))
bl = list(self._bl_listbox.get(0, tk.END))
export_data = {
"version": APP_VERSION,
"date_export": datetime.now().isoformat(),
"etablissement": "", # à remplir par l'utilisateur
"whitelist_phrases": wl,
"blacklist_force_mask_terms": bl,
"instructions": (
"Ce fichier contient les paramètres d'anonymisation personnalisés. "
"Envoyez-le par email à l'équipe technique pour mise à jour du programme."
),
}
# Proposer le Bureau comme destination par défaut
desktop = Path.home() / "Desktop"
if not desktop.exists():
desktop = Path.home() / "Bureau"
if not desktop.exists():
desktop = Path.home()
dest = filedialog.asksaveasfilename(
title="Exporter les paramètres",
initialdir=str(desktop),
initialfile="parametres_anonymisation.json",
defaultextension=".json",
filetypes=[("JSON", "*.json"), ("Tous", "*.*")],
)
if dest:
Path(dest).write_text(
_json.dumps(export_data, ensure_ascii=False, indent=2),
encoding="utf-8",
)
messagebox.showinfo(
"Export réussi",
f"Paramètres exportés dans :\n{dest}\n\n"
f"Vous pouvez envoyer ce fichier par email\n"
f"à l'équipe technique.",
)
except Exception as e:
messagebox.showerror("Erreur", f"Erreur à l'export :\n{e}")
def _import_params(self):
"""Importe des paramètres depuis un fichier JSON (fusionne avec l'existant)."""
try:
import json as _json
src = filedialog.askopenfilename(
title="Importer des paramètres",
filetypes=[("JSON", "*.json"), ("Tous", "*.*")],
)
if not src:
return
data = _json.loads(Path(src).read_text(encoding="utf-8"))
# Fusionner whitelist
new_wl = data.get("whitelist_phrases", [])
existing_wl = set(self._wl_listbox.get(0, tk.END))
added_wl = 0
for phrase in new_wl:
if phrase and phrase.strip() and phrase.strip() not in existing_wl:
self._wl_listbox.insert(tk.END, phrase.strip())
added_wl += 1
# Fusionner blacklist
new_bl = data.get("blacklist_force_mask_terms", [])
existing_bl = set(self._bl_listbox.get(0, tk.END))
added_bl = 0
for term in new_bl:
if term and str(term).strip() and str(term).strip() not in existing_bl:
self._bl_listbox.insert(tk.END, str(term).strip())
added_bl += 1
version = data.get("version", "?")
date_exp = data.get("date_export", "?")[:10]
messagebox.showinfo(
"Import réussi",
f"Paramètres importés (v{version}, {date_exp}) :\n\n"
f" + {added_wl} phrase(s) ajoutée(s) à la whitelist\n"
f" + {added_bl} terme(s) ajouté(s) à la blacklist\n\n"
f"Cliquez sur « Sauvegarder » pour appliquer.",
)
except Exception as e:
messagebox.showerror("Erreur", f"Erreur à l'import :\n{e}")
def _save_params(self):
"""Sauvegarde les whitelist/blacklist dans la config YAML."""
try: