Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Amélioration/phase1 #276

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified .DS_Store
Binary file not shown.
Binary file added .coverage
Binary file not shown.
2 changes: 2 additions & 0 deletions .flaskenv
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
FLASK_APP=app.server
FLASK_ENV=development
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ bin
include
lib
.Python
tests/

.envrc
__pycache__
4 changes: 4 additions & 0 deletions CACHEDIR.TAG
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Signature: 8a477f597d28d172789f06886806bc55
# This file is a cache directory tag created by Python virtualenv.
# For information about cache directory tags, see:
# https://bford.info/cachedir/
Empty file added app/__init__.py
Empty file.
6 changes: 6 additions & 0 deletions app/booking_manager/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# app/booking_manager/__init__.py

from .booking_service import BookingService
from .club_manager import ClubManager
from .competition_manager import CompetitionManager
from .data_loader import JSONDataLoader
36 changes: 36 additions & 0 deletions app/booking_manager/booking_service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# app/booking_manager/booking_service.py

from app.booking_manager.club_manager import ClubManager
from app.booking_manager.competition_manager import CompetitionManager


class BookingService:
"""
Ordonne le processus de réservation en utilisant les gestionnaires de clubs et de compétitions.
"""

def __init__(self, clubs_file: str, competitions_file: str):
self.club_manager = ClubManager(clubs_file)
self.competition_manager = CompetitionManager(competitions_file)

def purchase_places(self, club_name: str, competition_name: str, places_requested: int) -> bool:
club = self.club_manager.find_by_name(club_name)
competition = self.competition_manager.find_by_name(competition_name)

if not club or not competition:
return False
if places_requested > 12:
return False
if places_requested > club.points:
return False
if places_requested > competition.number_of_places:
return False

competition.number_of_places -= places_requested
club.points -= places_requested

# Sauvegarde des modifications dans les fichiers JSON
self.club_manager.save_clubs()
self.competition_manager.save_competitions()

return True
55 changes: 55 additions & 0 deletions app/booking_manager/club_manager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# app/booking_manager/club_manager.py

import json
from typing import List, Optional
from app.models import Club
from .data_loader import JSONDataLoader


class ClubManager:
"""
Gère le chargement, la recherche et la sauvegarde des clubs.
"""

def __init__(self, clubs_file: str):
self.clubs_file = clubs_file # Pour la sauvegarde
loader = JSONDataLoader(clubs_file)
data = loader.load_data()
self.clubs: List[Club] = self._parse_clubs(data)

def _parse_clubs(self, data: dict) -> List[Club]:
clubs = []
for c in data.get("clubs", []):
clubs.append(
Club(
name=c["name"],
email=c["email"],
points=int(c["points"]),
id=c.get("id")
)
)
return clubs

def find_by_email(self, email: str) -> Optional[Club]:
return next((club for club in self.clubs if club.email == email), None)

def find_by_name(self, name: str) -> Optional[Club]:
return next((club for club in self.clubs if club.name == name), None)

def save_clubs(self, filepath: Optional[str] = None) -> None:
"""
Sauvegarde l'état actuel des clubs dans un fichier JSON.
"""
if filepath is None:
filepath = self.clubs_file
clubs_data = {"clubs": []}
for club in self.clubs:
club_dict = {
"id": club.id,
"name": club.name,
"email": club.email,
"points": str(club.points)
}
clubs_data["clubs"].append(club_dict)
with open(filepath, "w") as f:
json.dump(clubs_data, f, indent=4)
50 changes: 50 additions & 0 deletions app/booking_manager/competition_manager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# app/booking_manager/competition_manager.py

import json
from typing import List, Optional
from app.models import Competition
from .data_loader import JSONDataLoader


class CompetitionManager:
"""
Gère le chargement, la recherche et la sauvegarde des compétitions.
"""

def __init__(self, competitions_file: str):
self.competitions_file = competitions_file # Pour la sauvegarde
loader = JSONDataLoader(competitions_file)
data = loader.load_data()
self.competitions: List[Competition] = self._parse_competitions(data)

def _parse_competitions(self, data: dict) -> List[Competition]:
competitions = []
for c in data.get("competitions", []):
competitions.append(
Competition(
name=c["name"],
date=c["date"],
number_of_places=int(c["numberOfPlaces"])
)
)
return competitions

def find_by_name(self, name: str) -> Optional[Competition]:
return next((comp for comp in self.competitions if comp.name == name), None)

def save_competitions(self, filepath: Optional[str] = None) -> None:
"""
Sauvegarde l'état actuel des compétitions dans un fichier JSON.
"""
if filepath is None:
filepath = self.competitions_file
competitions_data = {"competitions": []}
for comp in self.competitions:
comp_dict = {
"name": comp.name,
"date": comp.date,
"numberOfPlaces": str(comp.number_of_places)
}
competitions_data["competitions"].append(comp_dict)
with open(filepath, "w") as f:
json.dump(competitions_data, f, indent=4)
21 changes: 21 additions & 0 deletions app/booking_manager/data_loader.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# app/booking_manager/data_loader.py

import json
import os
from typing import Any


class JSONDataLoader:
"""
Classe générique pour charger des données depuis un fichier JSON.
"""

def __init__(self, filepath: str):
self.filepath = filepath

def load_data(self) -> Any:
if not os.path.exists(self.filepath):
raise FileNotFoundError(f"Fichier introuvable : {self.filepath}")
with open(self.filepath, "r") as f:
data = json.load(f)
return data
15 changes: 15 additions & 0 deletions app/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# app/models.py

class Competition:
def __init__(self, name, date, number_of_places):
self.name = name
self.date = date
self.number_of_places = number_of_places


class Club:
def __init__(self, name, email, points, id=None):
self.name = name
self.email = email
self.points = points
self.id = id
134 changes: 134 additions & 0 deletions app/server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
# app/server.py

from flask import Flask, render_template, request, redirect, flash, url_for, session
from app.booking_manager import BookingService

app = Flask(__name__, static_folder="../static")
app.secret_key = "secret_key_xyz"

# Instanciation du service de réservation
booking_service = BookingService(
clubs_file="data/clubs.json",
competitions_file="data/competitions.json"
)


@app.context_processor
def inject_club_email():
"""
Permet d'accéder à session['club_email'] dans les templates,
afin de savoir si un utilisateur est connecté.
"""
return dict(club_email=session.get('club_email'))


@app.route("/")
def index():
"""
Page d'accueil.
Si un utilisateur est connecté, on va quand même afficher index.html,
mais le bouton "Accueil" le renverra à showSummary.
"""
return render_template("index.html")


@app.route("/showSummary", methods=["POST"])
def show_summary():
"""
Récupère l'email du club et affiche la page de résumé (welcome.html).
Si le club n'est pas trouvé, on redirige avec un message d'erreur.
"""
email = request.form.get("email", "")
club = booking_service.club_manager.find_by_email(email)
if not club:
flash("Email inconnu ou invalide.")
return redirect(url_for("index"))

# Stocker l'email en session pour signifier "connecté"
session['club_email'] = club.email

competitions = booking_service.competition_manager.competitions
return render_template("welcome.html", club=club, competitions=competitions)


@app.route("/book/<competition>/<club>")
def book(competition, club):
"""
Affiche la page de réservation (booking.html) pour un club/compétition donné.
"""
found_competition = booking_service.competition_manager.find_by_name(
competition)
found_club = booking_service.club_manager.find_by_name(club)
if not found_competition or not found_club:
flash("Something went wrong - please try again")
return redirect(url_for("index"))

return render_template("booking.html", club=found_club, competition=found_competition)


@app.route("/purchasePlaces", methods=["POST"])
def purchase_places():
"""
Tente d'acheter 'places' places pour un club et une compétition.
"""
competition_name = request.form.get("competition")
club_name = request.form.get("club")
places_str = request.form.get("places")

try:
places_requested = int(places_str)
except ValueError:
flash("Le nombre de places est invalide.")
return redirect(url_for("index"))

found_competition = booking_service.competition_manager.find_by_name(
competition_name)
found_club = booking_service.club_manager.find_by_name(club_name)

if not found_competition or not found_club:
flash("Something went wrong - please try again")
return redirect(url_for("index"))

# Logique de réservation : on veut des messages plus précis
if places_requested > 12:
flash("Vous ne pouvez pas réserver plus de 12 places.")
success = False
else:
success = booking_service.purchase_places(
club_name, competition_name, places_requested)

if success:
flash(
f"Great-booking complete! Vous avez réservé {places_requested} places.")
else:
# On vérifie pourquoi c'est False :
# - Soit le club n'a pas assez de points
# - Soit la compétition n'a pas assez de places (ou est passée)
# Dans booking_service, on a un return False générique.
# On va personnaliser le message selon la situation :
if places_requested <= 12: # si ce n'est pas déjà la condition "plus de 12"
# On suppose que c'est un problème de places trop petites ou club pas assez de points
flash("Le concours est complet (ou il ne reste pas assez de places) ou vous n'avez pas assez de points.")

updated_club = booking_service.club_manager.find_by_name(club_name)
competitions = booking_service.competition_manager.competitions
return render_template("welcome.html", club=updated_club, competitions=competitions)


@app.route("/clubsPoints")
def clubs_points():
"""
Affiche la liste des clubs et leurs points disponibles.
Page publique, sans besoin de login.
"""
clubs = booking_service.club_manager.clubs
return render_template("clubs_points.html", clubs=clubs)


@app.route("/logout")
def logout():
"""
Déconnecte le club (retour à l'accueil).
"""
session.pop('club_email', None)
return redirect(url_for("index"))
44 changes: 44 additions & 0 deletions app/templates/base.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}GUDLFT{% endblock %}</title>
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
</head>
<body>
<nav>
<ul>
{% if club_email %}
<!-- Si connecté, 'Accueil' renvoie à showSummary, en POST on a besoin d'un form ou un GET param -->
<li>
<form action="{{ url_for('show_summary') }}" method="POST" style="display: inline;">

<input type="hidden" name="email" value="{{ club_email }}">
<button type="submit" style="background:none;border:none;color:blue;cursor:pointer;">
Accueil
</button>
</form>
</li>
{% else %}
<li><a href="{{ url_for('index') }}">Accueil</a></li>
{% endif %}
<li><a href="{{ url_for('clubs_points') }}">Points Clubs</a></li>
<li><a href="{{ url_for('logout') }}">Déconnexion</a></li>
</ul>
</nav>

<div class="container">
{% with messages = get_flashed_messages() %}
{% if messages %}
<ul class="flashes">
{% for message in messages %}
<li>{{ message }}</li>
{% endfor %}
</ul>
{% endif %}
{% endwith %}
{% block content %}{% endblock %}
</div>
</body>
</html>
Loading