site.toml [administration] adresse : l'accueil affiche un bouton « Connexion » vers l'interface. Celle-ci peut tourner derrière le serveur web du site (rtg interface --proxy --prefixe /admin) : adresses préfixées, mot de passe obligatoire, visiteur identifié par X-Forwarded-For, cookie Secure limité au préfixe quand le site est en HTTPS. Exemples nginx et Apache dans le README. Le bouton Arrêter ne remplace plus le bloc de la tâche en même temps que la relève. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Mf2kATMtqVABHSYdxJMnim
166 lines
6.5 KiB
Python
166 lines
6.5 KiB
Python
"""Page d'accueil du site et listes complètes par rubrique.
|
||
|
||
index.html une rubrique par section, avec les ACCUEIL_NB voyages les
|
||
plus récents (2 par défaut) et un lien « Tout voir »
|
||
voyages.html tous les voyages rangés à la racine du site
|
||
balades.html tous ceux du dossier Balades/ (une page par regroupement)
|
||
carte_globale.html les voyages (pas les balades) et les cartes My Maps
|
||
sur une même carte, affichée en grand sous les rubriques
|
||
de l'accueil (voir genere_carte_globale.py et mymaps.txt)
|
||
|
||
Appelé par `rtg generer` / `rtg accueil`. Réglages (site.toml) : titre,
|
||
accueil_nb_recents, carte_globale.mymaps.
|
||
|
||
Un voyage est un dossier qui contient mini.html (produit par genere_carte.py).
|
||
Les voyages rangés à la racine forment la rubrique « Voyages » ; ceux rangés
|
||
un niveau plus bas, dans un dossier de regroupement (Balades/Aisne-06-2026),
|
||
forment une rubrique du nom de ce dossier.
|
||
|
||
La moto d'un voyage (voyage.toml, ou ancien moto.txt) est affichée dans la
|
||
légende de la carte globale.
|
||
|
||
Les voyages sont triés du plus récent au plus ancien d'après les dates des
|
||
traces GPX (routes/AAAA-MM-JJ.gpx), sinon d'après l'année trouvée dans le nom.
|
||
"""
|
||
import os
|
||
import unicodedata
|
||
import re
|
||
from datetime import date
|
||
from urllib.parse import quote
|
||
|
||
from jinja2 import Environment, FileSystemLoader
|
||
|
||
from rtg.carte_globale import SORTIE as CARTE_GLOBALE, generer_carte_globale
|
||
from rtg.config import reglages_site, reglages_voyage
|
||
|
||
DOSSIER_TEMPLATES = os.path.join(os.path.dirname(os.path.abspath(__file__)), "templates")
|
||
EXCLURE = {'html', 'Scripts', 'RoadTripsGenerator', '__pycache__', 'node_modules',
|
||
'assets', 'videos', 'photos', 'photos_web', 'apercus', 'rushs', 'routes'}
|
||
TITRE_SECTION_RACINE = "Voyages"
|
||
MOIS = ["janvier", "février", "mars", "avril", "mai", "juin", "juillet",
|
||
"août", "septembre", "octobre", "novembre", "décembre"]
|
||
|
||
|
||
def sous_dossiers(dossier):
|
||
for d in sorted(os.listdir(dossier)):
|
||
chemin = os.path.join(dossier, d)
|
||
if d in EXCLURE or d.startswith(('.', '_')) or not os.path.isdir(chemin):
|
||
continue
|
||
yield d, chemin
|
||
|
||
|
||
def est_voyage(chemin):
|
||
return os.path.exists(os.path.join(chemin, 'mini.html'))
|
||
|
||
|
||
def dates_du_voyage(chemin):
|
||
routes = os.path.join(chemin, 'routes')
|
||
if not os.path.isdir(routes):
|
||
return []
|
||
dates = set()
|
||
for f in os.listdir(routes):
|
||
m = re.search(r'(\d{4})-(\d{2})-(\d{2})', f)
|
||
if m and not f.startswith('.'):
|
||
try:
|
||
dates.add(date(*map(int, m.groups())))
|
||
except ValueError:
|
||
pass
|
||
return sorted(dates)
|
||
|
||
|
||
def periode(dates):
|
||
"""« juin 2026 · 1 jour », « juillet – août 2025 · 15 jours »…"""
|
||
if not dates:
|
||
return ""
|
||
d1, d2 = dates[0], dates[-1]
|
||
if (d1.year, d1.month) == (d2.year, d2.month):
|
||
texte = f"{MOIS[d1.month - 1]} {d1.year}"
|
||
elif d1.year == d2.year:
|
||
texte = f"{MOIS[d1.month - 1]} – {MOIS[d2.month - 1]} {d2.year}"
|
||
else:
|
||
texte = f"{MOIS[d1.month - 1]} {d1.year} – {MOIS[d2.month - 1]} {d2.year}"
|
||
n = len(dates)
|
||
return f"{texte} · {n} jour{'s' if n > 1 else ''}"
|
||
|
||
|
||
def fiche(racine, chemin):
|
||
nom = os.path.basename(chemin)
|
||
dates = dates_du_voyage(chemin)
|
||
if dates:
|
||
quand = dates[0]
|
||
else:
|
||
m = re.search(r'(19|20)\d{2}', nom)
|
||
quand = date(int(m.group(0)), 1, 1) if m else date.min
|
||
return {
|
||
'dossier': chemin,
|
||
'titre': reglages_voyage(chemin).get('titre') or nom.replace('-', ' ').replace('_', ' '),
|
||
'chemin': quote(os.path.relpath(chemin, racine).replace(os.sep, '/')),
|
||
'periode': periode(dates),
|
||
'moto': reglages_voyage(chemin).get('moto', ''),
|
||
'quand': quand,
|
||
}
|
||
|
||
|
||
def trier(voyages):
|
||
# Plus récent d'abord ; à date égale, ordre alphabétique.
|
||
voyages.sort(key=lambda v: v['titre'].lower())
|
||
voyages.sort(key=lambda v: v['quand'], reverse=True)
|
||
return voyages
|
||
|
||
|
||
def nom_de_page(titre):
|
||
"""« Balades » → « balades.html » (minuscules, sans accents ni espaces)."""
|
||
t = unicodedata.normalize('NFKD', titre).encode('ascii', 'ignore').decode().lower()
|
||
return re.sub(r'[^a-z0-9]+', '-', t).strip('-') + '.html'
|
||
|
||
|
||
def rubrique(titre, voyages):
|
||
n = len(voyages)
|
||
unite = titre.lower() if n > 1 else titre.lower().rstrip('s')
|
||
return {'titre': titre, 'voyages': trier(voyages), 'page': nom_de_page(titre), 'unite': unite}
|
||
|
||
|
||
def collecter_rubriques(racine):
|
||
principaux, rubriques = [], []
|
||
for nom, chemin in sous_dossiers(racine):
|
||
if est_voyage(chemin):
|
||
principaux.append(fiche(racine, chemin))
|
||
else:
|
||
voyages = [fiche(racine, c) for _, c in sous_dossiers(chemin) if est_voyage(c)]
|
||
if voyages:
|
||
rubriques.append(rubrique(nom.replace('-', ' ').replace('_', ' '), voyages))
|
||
if principaux:
|
||
rubriques.insert(0, rubrique(TITRE_SECTION_RACINE, principaux))
|
||
return rubriques
|
||
|
||
|
||
def generer(racine=".", cfg=None, mettre_a_jour_mymaps=False):
|
||
racine = os.path.abspath(racine)
|
||
cfg = cfg or reglages_site(racine)
|
||
TITRE_SITE, NB_RECENTS = cfg["titre"], int(cfg["accueil_nb_recents"])
|
||
rubriques = collecter_rubriques(racine)
|
||
total = sum(len(r['voyages']) for r in rubriques)
|
||
|
||
env = Environment(loader=FileSystemLoader(DOSSIER_TEMPLATES), autoescape=True,
|
||
trim_blocks=True, lstrip_blocks=True)
|
||
|
||
def ecrire(nom, contenu):
|
||
with open(os.path.join(racine, nom), "w", encoding="utf-8") as f:
|
||
f.write(contenu)
|
||
|
||
ecrire("index.html", env.get_template("accueil.html").render(
|
||
titre_site=TITRE_SITE, rubriques=rubriques, nb_recents=NB_RECENTS,
|
||
administration=cfg["administration"]["adresse"],
|
||
carte_globale=CARTE_GLOBALE if any(r['titre'] == TITRE_SECTION_RACINE for r in rubriques) else None,
|
||
sous_titre=f"{total} voyage{'s' if total > 1 else ''}" if total else ""))
|
||
for r in rubriques:
|
||
ecrire(r['page'], env.get_template("liste.html").render(titre_site=TITRE_SITE, r=r))
|
||
|
||
# Carte globale : la rubrique Voyages seulement, sans les balades.
|
||
voyages = [r for r in rubriques if r['titre'] == TITRE_SECTION_RACINE]
|
||
if voyages:
|
||
generer_carte_globale(racine, voyages, cfg["carte_globale"]["mymaps"], mettre_a_jour_mymaps)
|
||
|
||
detail = ", ".join(f"{r['titre']} : {len(r['voyages'])} → {r['page']}" for r in rubriques)
|
||
print(f"🌍 Accueil : {total} voyage(s) ({detail})")
|