Skip to content

Commit

Permalink
Resolve OpenClassrooms-Student-Center#2 : Club Points validation
Browse files Browse the repository at this point in the history
  • Loading branch information
geo1310 committed Apr 16, 2024
1 parent d6b42e9 commit 9edf726
Show file tree
Hide file tree
Showing 10 changed files with 217 additions and 62 deletions.
35 changes: 19 additions & 16 deletions gudlift_reservation/data/clubs.json
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
{"clubs":[
{
"name":"Simply Lift",
"email":"john@simplylift.co",
"points":"13"
},
{
"name":"Iron Temple",
"email": "admin@irontemple.com",
"points":"4"
},
{ "name":"She Lifts",
"email": "kate@shelifts.co.uk",
"points":"12"
}
]}
{
"clubs": [
{
"name": "Simply pytestLift",
"email": "john@simplylift.co",
"points": 13
},
{
"name": "Iron Temple",
"email": "admin@irontemple.com",
"points": 4
},
{
"name": "She Lifts",
"email": "kate@shelifts.co.uk",
"points": 12
}
]
}
4 changes: 2 additions & 2 deletions gudlift_reservation/data/competitions.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@
{
"name": "Spring Festival",
"date": "2020-03-27 10:00:00",
"numberOfPlaces": "25"
"numberOfPlaces": 25
},
{
"name": "Fall Classic",
"date": "2020-10-22 13:30:00",
"numberOfPlaces": "13"
"numberOfPlaces": 13
}
]
}
48 changes: 48 additions & 0 deletions gudlift_reservation/json_handler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import json
import os


def load_data(file_name):
"""
Charge les données à partir d'un fichier JSON.
Args:
file_name (str): Le nom du fichier JSON.
Returns:
dict: Les données chargées depuis le fichier JSON.
"""
current_dir = os.path.dirname(__file__)
file_path = os.path.join(current_dir, "data", file_name)
with open(file_path) as f:
return json.load(f)


def save_data(data, file_name):
"""
Enregistre les données dans un fichier JSON.
Args:
data (dict): Les données à enregistrer.
file_name (str): Le nom du fichier JSON où enregistrer les données.
"""
current_dir = os.path.dirname(__file__)
file_path = os.path.join(current_dir, "data", file_name)
with open(file_path, "w") as f:
json.dump(data, f)


def load_clubs():
return load_data("clubs.json")["clubs"]


def load_competitions():
return load_data("competitions.json")["competitions"]


def save_clubs(clubs):
save_data({"clubs": clubs}, "clubs.json")


def save_competitions(competitions):
save_data({"competitions": competitions}, "competitions.json")
66 changes: 45 additions & 21 deletions gudlift_reservation/server.py
Original file line number Diff line number Diff line change
@@ -1,41 +1,32 @@
import json
import os

from flask import (Flask, abort, flash, redirect, render_template, request,
url_for)

from .json_handler import (load_clubs, load_competitions, save_clubs,
save_competitions)

app = Flask(__name__)
app.config.from_object("gudlift_reservation.config")


def load_clubs():
current_dir = os.path.dirname(__file__)
clubs_file_path = os.path.join(current_dir, "data", "clubs.json")
with open(clubs_file_path) as c:
list_of_clubs = json.load(c)["clubs"]
return list_of_clubs


def load_competitions():
current_dir = os.path.dirname(__file__)
competitions_file_path = os.path.join(current_dir, "data", "competitions.json")
with open(competitions_file_path) as comps:
list_of_competitions = json.load(comps)["competitions"]
return list_of_competitions


competitions = load_competitions()
clubs = load_clubs()
welcome_template = "welcome.html"


@app.route("/")
def index():
"""
Affiche la page d'accueil
"""
return render_template("index.html")


@app.route("/showSummary", methods=["POST"])
def show_summary():
"""
Affiche le résumé des informations pour un club donné.
Vérifie si l'email est présent et correspond à un club.
"""

email = request.form["email"]

Expand All @@ -54,6 +45,10 @@ def show_summary():

@app.route("/book/<competition>/<club>")
def book(competition, club):
"""
Affiche la page de réservation pour une compétition et un club donné.
Vérifie si la compétition et le club sont valides.
"""

try:
found_club = next(c for c in clubs if c["name"] == club)
Expand All @@ -72,6 +67,13 @@ def book(competition, club):

@app.route("/purchasePlaces", methods=["POST"])
def purchase_places():
"""
Gère la réservation de places pour une compétition par un club donné.
Vérifie si la compétition et le club sont valides, puis vérifie si le club a suffisamment
de points pour acheter des places et si le nb de places demandées est positif.
Si le club a suffisamment de points, les places sont réservées et les points du club et les
places de la compétition sont mis à jour.
"""

try:
competition = next(
Expand All @@ -86,15 +88,33 @@ def purchase_places():

try:
places_required = int(request.form["places"])
if places_required <= 0:
flash("Number of places required must be positive", "error")
return redirect(
url_for("book", competition=competition["name"], club=club["name"])
)

except ValueError:
flash("Invalid number", "error")
return redirect(
url_for("book", competition=competition["name"], club=club["name"])
)

competition["numberOfPlaces"] = int(competition["numberOfPlaces"]) - places_required
flash("Great-booking complete!")
if places_required <= club["points"]:

competition["numberOfPlaces"] -= places_required
club["points"] -= places_required

save_clubs(clubs)
save_competitions(competitions)

flash("Great-booking complete!")
else:
flash("insufficient number of points", "error")
return redirect(
url_for("book", competition=competition["name"], club=club["name"])
)

return render_template(welcome_template, club=club, competitions=competitions)


Expand All @@ -103,4 +123,8 @@ def purchase_places():

@app.route("/logout")
def logout():
"""
Déconnecte l'utilisateur et redirige vers la page d'accueil.
"""

return redirect(url_for("index"))
3 changes: 3 additions & 0 deletions gudlift_reservation/static/css/style.css
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
body {
margin-left: 20px;
}
button {
margin-top: 20px;
}
.flash-error {
color: red;
font-weight: 700;
Expand Down
4 changes: 4 additions & 0 deletions gudlift_reservation/templates/booking.html
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,9 @@ <h2>{{competition['name']}}</h2>
{% endif %}
{% endwith %}

<form action="/showSummary" method="post">
<input type="hidden" name="email" value="{{club['email']}}">
<button type="submit">Return Summary</button>
</form>
</body>
</html>
2 changes: 1 addition & 1 deletion gudlift_reservation/templates/welcome.html
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ <h3>Competitions:</h3>
{{comp['name']}}<br />
Date: {{comp['date']}}</br>
Number of Places: {{comp['numberOfPlaces']}}
{%if comp['numberOfPlaces']|int >0%}
{%if comp['numberOfPlaces'] >0 and club['points'] >0%}
<a href="{{ url_for('book',competition=comp['name'],club=club['name']) }}">Book Places</a>
{%endif%}
</li>
Expand Down
25 changes: 25 additions & 0 deletions gudlift_reservation/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from gudlift_reservation import app, server
from gudlift_reservation.json_handler import save_clubs, save_competitions


def setup_class(cls):
"""
Méthode de configuration de classe exécutée une seule fois avant tous les tests.
Initialise un client de test Flask
Charge les données des clubs et des compétitions.
Cree des sauvegardes de clubs.json et competitions.json
"""
cls.client = app.test_client()
cls.clubs = server.load_clubs()
cls.competitions = server.load_competitions()
cls.clubs_save = cls.clubs
cls.competitions_save = cls.competitions


def teardown_method(self):
"""
Méthode de configuration executée a la fin des tests
Rétablit les fichiers json d'origine.
"""
save_clubs(self.clubs_save)
save_competitions(self.competitions_save)
57 changes: 57 additions & 0 deletions gudlift_reservation/tests/test_calculation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import pytest

from gudlift_reservation.json_handler import load_clubs

from . import setup_class, teardown_method


class TestCalculation:
"""
Classe de tests pour tester les calculs de l'application.
"""

@classmethod
def setup_class(cls):
setup_class(cls)

def teardown_method(self):
teardown_method(self)

@pytest.mark.parametrize(
"club_name, competition_name, places, expected_value",
[
("Iron Temple", "Spring Festival", 1, "Great-booking complete!"),
("Iron Temple", "Spring Festival", 100, "insufficient number of points"),
("Iron Temple", "Spring Festival", -100, "Number of places required must be positive"),
],
)
def test_club_purchase_places_calculation(
self, club_name, competition_name, places, expected_value
):
"""
Verifie l'utilisation des points d'un club.
Le nombre de points demandés doit etre positif.
Les points du club doivent rester positif ou nul.
Les points utilisés doivent etre déduits des points du club.
"""

club = next(club for club in self.clubs if club["name"] == club_name)
club_points_before = club["points"]

rv = self.client.post(
"/purchasePlaces",
data={"competition": competition_name, "club": club_name, "places": places},
follow_redirects=True
)

self.clubs = load_clubs()
club = next(club for club in self.clubs if club["name"] == club_name)
club_points_after = club["points"]
expected_points = club_points_before - places

assert rv.status_code == 200
assert expected_value.encode("utf-8") in rv.data
assert club_points_after >= 0

if expected_value == "Great-booking complete!":
assert club_points_after == expected_points
Loading

0 comments on commit 9edf726

Please sign in to comment.