From 1a8f5c2b74c428e52250079b3e6f9ff1985789f0 Mon Sep 17 00:00:00 2001 From: r3n3gat Date: Fri, 26 Sep 2025 08:41:30 +0200 Subject: [PATCH 01/11] modif html --- server.py | 185 +++++++++++++++++++++++++++++++---------- templates/booking.html | 15 ++++ templates/index.html | 16 ++++ templates/welcome.html | 15 ++++ 4 files changed, 187 insertions(+), 44 deletions(-) diff --git a/server.py b/server.py index 4084baeac..a38335d6c 100644 --- a/server.py +++ b/server.py @@ -1,59 +1,156 @@ +# server.py import json -from flask import Flask,render_template,request,redirect,flash,url_for - +from datetime import datetime +from flask import Flask, render_template, request, redirect, flash, url_for, session +# ---------- Data loading ---------- def loadClubs(): - with open('clubs.json') as c: - listOfClubs = json.load(c)['clubs'] - return listOfClubs - + with open("clubs.json", "r", encoding="utf-8") as c: + return json.load(c)["clubs"] def loadCompetitions(): - with open('competitions.json') as comps: - listOfCompetitions = json.load(comps)['competitions'] - return listOfCompetitions - + with open("competitions.json", "r", encoding="utf-8") as comps: + return json.load(comps)["competitions"] app = Flask(__name__) -app.secret_key = 'something_special' +app.secret_key = "something_secret_for_flash" # NOTE: en prod, utiliser une variable d'env -competitions = loadCompetitions() +# Données en mémoire (réinitialisées dans les tests via conftest) clubs = loadClubs() +competitions = loadCompetitions() -@app.route('/') +# suivi cumulatif des réservations par (club, competition) +# clé: (club_name, comp_name) -> int +club_bookings = {} + +# ---------- Helpers ---------- +def find_club_by_name(name): + return next((c for c in clubs if c.get("name") == name), None) + +def find_club_by_email(email_value): + if not email_value: + return None + return next((c for c in clubs if c.get("email", "").lower() == email_value.lower()), None) + +def find_comp_by_name(name): + return next((c for c in competitions if c.get("name") == name), None) + +def competition_in_past(competition) -> bool: + """True si la compétition est passée. Tolérant aux dates mal formées.""" + try: + comp_dt = datetime.strptime(competition["date"], "%Y-%m-%d %H:%M:%S") + return comp_dt < datetime.now() + except Exception: + return False + +def validate_purchase(club, competition, places_required, current_booked=0): + """ + Règles: + - compétition non passée + - places_required entier > 0 + - places_required <= places restantes + - points du club >= places_required + - plafond 12 (cumul par club/compétition) + """ + # 1) compétition non passée + if competition_in_past(competition): + return False, "Competition already finished" + + # 2) quantité valide + try: + n = int(places_required) + except (TypeError, ValueError): + return False, "Invalid quantity" + if n <= 0: + return False, "Invalid quantity (>=1)" + + # 3) places restantes + comp_left = int(competition["numberOfPlaces"]) + if n > comp_left: + return False, "Pas assez de places disponibles" + + # 4) points du club + club_pts = int(club["points"]) + if n > club_pts: + return False, "Pas assez de points" + + # 5) plafond 12 cumulé + if current_booked + n > 12: + return False, "Maximum 12 places par club sur une compétition" + + return True, None + +# ---------- Routes ---------- +@app.route("/") def index(): - return render_template('index.html') + # index doit afficher les messages flash ("Email inconnu", etc.) + return render_template("index.html") -@app.route('/showSummary',methods=['POST']) +@app.route("/showSummary", methods=["POST"]) def showSummary(): - club = [club for club in clubs if club['email'] == request.form['email']][0] - return render_template('welcome.html',club=club,competitions=competitions) - - -@app.route('/book//') -def book(competition,club): - foundClub = [c for c in clubs if c['name'] == club][0] - foundCompetition = [c for c in competitions if c['name'] == competition][0] - if foundClub and foundCompetition: - return render_template('booking.html',club=foundClub,competition=foundCompetition) - else: - flash("Something went wrong-please try again") - return render_template('welcome.html', club=club, competitions=competitions) - - -@app.route('/purchasePlaces',methods=['POST']) + email_input = (request.form.get("email") or "").strip() + club = next((c for c in clubs if c.get("email") == email_input), None) + if not club: + flash("Email inconnu") + return redirect(url_for("index")) + # mémoriser le club connecté en session (sert aux tests 'club-reservation' & >12 cumulative) + session["club_email"] = club["email"].lower() + return render_template("welcome.html", club=club, competitions=competitions) + +@app.route("/book//") +def book(competition, club): + foundClub = find_club_by_name(club) + foundCompetition = find_comp_by_name(competition) + if not foundClub or not foundCompetition: + flash("Club ou compétition introuvable") + return redirect(url_for("index")) + return render_template("booking.html", club=foundClub, competition=foundCompetition) + +@app.route("/purchasePlaces", methods=["POST"]) def purchasePlaces(): - competition = [c for c in competitions if c['name'] == request.form['competition']][0] - club = [c for c in clubs if c['name'] == request.form['club']][0] - placesRequired = int(request.form['places']) - competition['numberOfPlaces'] = int(competition['numberOfPlaces'])-placesRequired - flash('Great-booking complete!') - return render_template('welcome.html', club=club, competitions=competitions) - - -# TODO: Add route for points display - - -@app.route('/logout') + comp_name = request.form.get("competition") + places_str = request.form.get("places", "0") + + # récupérer le club depuis la session (le formulaire peut ne pas l'envoyer) + club_email = session.get("club_email") + club = find_club_by_email(club_email) + competition = find_comp_by_name(comp_name) if comp_name else None + + if not competition or not club: + # Le test cherche le mot "invalides" dans le message d'erreur + flash("Données invalides (club/compétition invalides)") + return redirect(url_for("index")) + + booked = club_bookings.get((club["name"], competition["name"]), 0) + ok, msg = validate_purchase(club, competition, places_str, current_booked=booked) + if not ok: + flash(msg) + # On reste sur la page du club (welcome), status 200 attendu par les tests + return render_template("welcome.html", club=club, competitions=competitions) + + n = int(places_str) + + # appliquer la réservation (cohérence points/places) + new_places = int(competition["numberOfPlaces"]) - n + new_points = int(club["points"]) - n + if new_places < 0 or new_points < 0: + # sauvegarde défensive (ne devrait pas arriver si la validation est OK) + flash("Erreur de calcul des points/places") + return render_template("welcome.html", club=club, competitions=competitions) + + competition["numberOfPlaces"] = str(new_places) + club["points"] = str(new_points) + club_bookings[(club["name"], competition["name"])] = booked + n + + flash("Great-booking complete!") + return render_template("welcome.html", club=club, competitions=competitions) + +@app.route("/points") +def points(): + # tableau public (pas de login requis) + return render_template("points.html", clubs=clubs) + +@app.route("/logout") def logout(): - return redirect(url_for('index')) \ No newline at end of file + session.clear() + return redirect(url_for("index")) diff --git a/templates/booking.html b/templates/booking.html index 06ae1156c..d6579e05d 100644 --- a/templates/booking.html +++ b/templates/booking.html @@ -5,6 +5,21 @@ Booking for {{competition['name']}} || GUDLFT + {% with messages = get_flashed_messages() %} + {% if messages %} +
    + {% for message in messages %} +
  • {{ message }}
  • + {% endfor %} +
+ {% endif %} + {% endwith %} + + +

{{competition['name']}}

Places available: {{competition['numberOfPlaces']}}
diff --git a/templates/index.html b/templates/index.html index 926526b7d..b74cd4c9d 100644 --- a/templates/index.html +++ b/templates/index.html @@ -5,6 +5,22 @@ GUDLFT Registration + {% with messages = get_flashed_messages() %} + {% if messages %} +
    + {% for message in messages %} +
  • {{ message }}
  • + {% endfor %} +
+ {% endif %} + {% endwith %} + + + +

Welcome to the GUDLFT Registration Portal!

Please enter your secretary email to continue: diff --git a/templates/welcome.html b/templates/welcome.html index ff6b261a2..387831681 100644 --- a/templates/welcome.html +++ b/templates/welcome.html @@ -5,6 +5,21 @@ Summary | GUDLFT Registration + {% with messages = get_flashed_messages() %} + {% if messages %} +
    + {% for message in messages %} +
  • {{ message }}
  • + {% endfor %} +
+ {% endif %} + {% endwith %} + + +

Welcome, {{club['email']}}

Logout {% with messages = get_flashed_messages()%} From 9c224f10ced25ea84f8a2d0ff94bf06e7377ac7e Mon Sep 17 00:00:00 2001 From: r3n3gat Date: Fri, 26 Sep 2025 08:43:33 +0200 Subject: [PATCH 02/11] test: explicit overspend points rule --- .idea/workspace.xml | 64 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 .idea/workspace.xml diff --git a/.idea/workspace.xml b/.idea/workspace.xml new file mode 100644 index 000000000..a01a6bba9 --- /dev/null +++ b/.idea/workspace.xml @@ -0,0 +1,64 @@ + + + + + + + + + + { + "associatedIndex": 4 +} + + + + { + "keyToString": { + "ModuleVcsDetector.initialDetectionPerformed": "true", + "RunOnceActivity.ShowReadmeOnStart": "true", + "RunOnceActivity.git.unshallow": "true", + "git-widget-placeholder": "bug/overspend-points", + "ignore.virus.scanning.warn.message": "true", + "last_opened_file_path": "C:/Users/user/dev/OPENCLASSROOMS2025/projet10_gudlft/Python_Testing" + } +} + + + + + + + + + + 1757788447965 + + + + + + + + \ No newline at end of file From 996d406753aa2c915bdb4ec680d2d016ee4db5f7 Mon Sep 17 00:00:00 2001 From: r3n3gat Date: Fri, 26 Sep 2025 08:48:44 +0200 Subject: [PATCH 03/11] chore: CLI script to print clubs & points table --- tools/print_points.py | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 tools/print_points.py diff --git a/tools/print_points.py b/tools/print_points.py new file mode 100644 index 000000000..3a7a7291e --- /dev/null +++ b/tools/print_points.py @@ -0,0 +1,11 @@ +# tools/print_points.py +import json + +with open("clubs.json", "r", encoding="utf-8") as f: + clubs = json.load(f)["clubs"] + +# tableau Markdown +print("| Club | Points |") +print("|------|--------|") +for c in clubs: + print(f"| {c['name']} | {c['points']} |") From 21288fcf43da95b1880bdccccf93b28b9ba484e0 Mon Sep 17 00:00:00 2001 From: r3n3gat Date: Fri, 26 Sep 2025 09:10:15 +0200 Subject: [PATCH 04/11] correctif pour test email --- server.py | 3 ++- templates/index.html | 55 ++++++++++++++++++++++---------------------- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/server.py b/server.py index a38335d6c..315a2e023 100644 --- a/server.py +++ b/server.py @@ -91,12 +91,13 @@ def showSummary(): email_input = (request.form.get("email") or "").strip() club = next((c for c in clubs if c.get("email") == email_input), None) if not club: + flash("Adresse mail inconnue") flash("Email inconnu") return redirect(url_for("index")) - # mémoriser le club connecté en session (sert aux tests 'club-reservation' & >12 cumulative) session["club_email"] = club["email"].lower() return render_template("welcome.html", club=club, competitions=competitions) + @app.route("/book//") def book(competition, club): foundClub = find_club_by_name(club) diff --git a/templates/index.html b/templates/index.html index b74cd4c9d..35ee6031b 100644 --- a/templates/index.html +++ b/templates/index.html @@ -1,32 +1,31 @@ + - - - - GUDLFT Registration - - - {% with messages = get_flashed_messages() %} - {% if messages %} -
    - {% for message in messages %} -
  • {{ message }}
  • - {% endfor %} -
- {% endif %} - {% endwith %} + + + + Güdlft – Login + + +{% with messages = get_flashed_messages() %} + {% if messages %} +
    + {% for message in messages %} +
  • {{ message }}
  • + {% endfor %} +
+ + {% endif %} +{% endwith %} - - +

Welcome to Güdlft

-

Welcome to the GUDLFT Registration Portal!

- Please enter your secretary email to continue: - - - - + + + + - - \ No newline at end of file + + From ae5c302e7d93d4ca9cd3935d53e2e55662c63fff Mon Sep 17 00:00:00 2001 From: r3n3gat Date: Fri, 26 Sep 2025 09:11:25 +0200 Subject: [PATCH 05/11] fix(auth): normalize email and show 'Adresse mail inconnue' --- .idea/workspace.xml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.idea/workspace.xml b/.idea/workspace.xml index a01a6bba9..0da842c14 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -4,7 +4,11 @@