Interface locale : rtg interface (voyages, sources, journées, récit, génération)

Serveur web local sans dépendance nouvelle (http.server, Jinja2, htmx et
Leaflet fournis). Écrans : Mes voyages, nouveau voyage, un voyage (journées
avec carte, rattachement des traces sans heures à un jour avec suggestion
d'après les photos, sources avec choix de dossier, infos et récit,
génération avec avancement et arrêt), réglages du site et vérification
des outils.

Chaîne : jours imposés ([dates_traces] dans voyage.toml), arrêt propre de
la compression, suivi des étapes, état structuré (site.tableau_etat),
titre du voyage repris de voyage.toml sur l'accueil.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Mf2kATMtqVABHSYdxJMnim
This commit is contained in:
FabriceandClaude Opus 5.5 committed 2026-10-09 14:07:47 +00:00
1 parent 608334b9d8
commit 06153a227e
37 files changed
+2618 -57

No files matched your search

+100
View File
@@ -0,0 +1,100 @@
"""Interface : jours imposés aux traces sans heures, suggestion d'après les
photos, et quelques échanges avec le serveur local."""
import os
import threading
import urllib.parse
import urllib.request
from datetime import date
from http.server import ThreadingHTTPServer
import pytest
from rtg import config, sources
from rtg.interface import serveur
from test_chaine import _photo, pytestmark # noqa: F401 (mêmes prérequis)
def _etape(chemin, lat0, lon0):
pts = "".join(f'<trkpt lat="{lat0 + i * 0.01:.5f}" lon="{lon0 + i * 0.012:.5f}"></trkpt>' for i in range(50))
with open(chemin, "w") as f:
f.write(f'<?xml version="1.0"?><gpx version="1.1" creator="68degres.fr" '
f'xmlns="http://www.topografix.com/GPX/1/1"><trk><trkseg>{pts}</trkseg></trk></gpx>')
@pytest.fixture
def voyage_sans_heures(tmp_path):
racine = tmp_path / "site"
voyage = racine / "Ecosse-2027"
gpx, photos = tmp_path / "gpx", tmp_path / "photos"
for d in (voyage, gpx, photos):
d.mkdir(parents=True)
_etape(gpx / "Etape 1.gpx", 55.9, 3.2)
_etape(gpx / "Etape 2.gpx", 56.7, 2.24)
_photo(str(photos / "P1.jpg"), "2027:06:11 10:00:00", gps=(56.75, 2.30))
config.ecrire_toml(str(racine / "site.toml"), {"titre": "Test"})
config.ecrire_toml(str(voyage / "voyage.toml"), {
"titre": "Écosse", "sources": {"traces": [str(gpx)], "videos": [], "photos": [str(photos)]}})
return racine, voyage
def _cfg(racine, voyage):
return config.reglages_voyage(str(voyage), config.reglages_site(str(racine)))
def test_jour_impose_et_suggestion(voyage_sans_heures):
racine, voyage = voyage_sans_heures
a = sources.analyser(str(voyage), _cfg(racine, voyage))
assert not a.journees and len(a.non_datees) == 2
medias = sources.positions_medias(str(voyage), a)
par_nom = {os.path.basename(f): p for f, p in a.non_datees.items()}
assert sources.suggerer_jour(par_nom["Etape 2.gpx"], medias) == (date(2027, 6, 11), 1)
assert sources.suggerer_jour(par_nom["Etape 1.gpx"], medias) == (None, 0)
config.modifier_toml(str(voyage / "voyage.toml"), {"dates_traces": {"Etape 1.gpx": "2027-06-10"}})
a = sources.analyser(str(voyage), _cfg(racine, voyage))
assert sorted(a.journees) == [date(2027, 6, 10)]
assert [os.path.basename(f) for f in a.non_datees] == ["Etape 2.gpx"]
# Le titre et les sources ont survécu à la réécriture du fichier
assert config.lire_toml(str(voyage / "voyage.toml"))["titre"] == "Écosse"
@pytest.fixture
def client(voyage_sans_heures):
racine, voyage = voyage_sans_heures
app = serveur.Application(str(racine))
srv = ThreadingHTTPServer(("127.0.0.1", 0), None)
port = srv.server_address[1]
srv.RequestHandlerClass = serveur.creer_gestionnaire(app, port)
threading.Thread(target=srv.serve_forever, daemon=True).start()
def appel(chemin, donnees=None, htmx=True):
req = urllib.request.Request(f"http://127.0.0.1:{port}{chemin}",
data=urllib.parse.urlencode(donnees).encode() if donnees else None)
if htmx:
req.add_header("HX-Request", "true")
try:
with urllib.request.urlopen(req) as r:
return r.status, r.read().decode()
except urllib.error.HTTPError as e:
return e.code, e.read().decode()
yield appel, racine, voyage
srv.shutdown()
def test_serveur(client):
appel, racine, voyage = client
code, html = appel("/")
assert code == 200 and "Écosse" in html
code, html = appel("/voyage/analyse?v=Ecosse-2027")
assert "2 traces à rattacher" in html and "11 juin 2027" in html
# Répartir : un jour par trace dans l'ordre des noms
code, html = appel("/voyage/dates-suite", {"v": "Ecosse-2027", "debut": "2027-06-10"})
assert "2 journées" in html
assert config.lire_toml(str(voyage / "voyage.toml"))["dates_traces"] == {
"Etape 1.gpx": "2027-06-10", "Etape 2.gpx": "2027-06-11"}
# Requête de modification sans l'en-tête htmx : refusée
code, _ = appel("/voyage/date", {"v": "Ecosse-2027", "fichier": "Etape 1.gpx", "jour": ""}, htmx=False)
assert code == 403
# Voyage hors du site : refusé
code, html = appel("/voyage/analyse?v=../../etc")
assert "introuvable" in html.lower()