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

Ajout de paramètres de limitation #67

Merged
merged 9 commits into from
Jun 21, 2024
Merged
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
51 changes: 50 additions & 1 deletion backend/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,15 @@
from marshmallow.exceptions import ValidationError as MarshmallowValidationError

from core.env import db
from core.routes import app_routes, QueryParamValidationError, EventIsFull
from core.routes import app_routes, QueryParamValidationError
from core.exceptions import (
EventIsFull,
UserEventNbExceded,
UserEventNbExcededUser,
UserEventNbExcededAdmin,
NotBookable,
ParticipantNbExceded,
)

mail = None

Expand Down Expand Up @@ -72,6 +80,47 @@ def handle_event_is_full_error(e):
422,
)

@app.errorhandler(NotBookable)
def handle_event_is_full_error(e):
return (
jsonify({"error": "L'animation n'est pas ouverte à la réservation"}),
422,
)

@app.errorhandler(UserEventNbExceded)
@app.errorhandler(UserEventNbExcededAdmin)
def handle_user_event_nb_exceded_error(e):
return (
jsonify(
{
"error": "La limite du nombre de réservation pour cet utilisateur est atteinte"
}
),
422,
)

@app.errorhandler(UserEventNbExcededUser)
def handle_user_event_nb_exceded_error(e):
return (
jsonify(
{
"error": "Vous avez atteint la limite du nombre de réservations possible par personne"
}
),
422,
)

@app.errorhandler(ParticipantNbExceded)
def handle_participant_nb_exceded_error(e):
return (
jsonify(
{
"error": "Vous ne pouvez pas inscrire autant de personnes sur une animation"
}
),
422,
)

@app.template_filter()
def format_date(value):
format_string = "EEEE d MMMM"
Expand Down
4 changes: 4 additions & 0 deletions backend/config/config.py.sample
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ ORGANISM_FOR_EMAIL_SUBJECT = ""

# Le nombre maximale de personnes que l'on peut mettre en liste d'attente sur un événement.
LISTE_ATTENTE_CAPACITY = 10
# Le nombre maximal d'animations auxquelles une personne peut s'inscrire
NB_ANIM_MAX_PER_USER = 3
# Le nombre maximal de participants que l'on peut indiquer lors de la création d'une réservation
NB_PARTICIPANTS_MAX_PER_ANIM_PER_USER = 10

# L'adresse où écoute le frontend du portail de réservation, par exemple : 'www.png-resa.fr' ou 'localhost:5000'.
PUBLIC_SERVER_NAME =
Expand Down
4 changes: 4 additions & 0 deletions backend/config/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@

# Le nombre maximale de personnes que l'on peut mettre en liste d'attente sur un événement.
LISTE_ATTENTE_CAPACITY = 10
# Le nombre maximal d'animations auxquelles une personne peut s'inscrire
NB_ANIM_MAX_PER_USER = 3
# Le nombre maximale de participant pouvant s'inscrire à une animation
NB_PARTICIPANTS_MAX_PER_ANIM_PER_USER = 10
# L'adresse où écoute le frontend du portail de réservation, par exemple : 'www.png-resa.fr' ou 'localhost:5000'.
PUBLIC_SERVER_NAME = "http://localhost:5173"

Expand Down
22 changes: 22 additions & 0 deletions backend/core/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
class EventIsFull(Exception):
pass


class NotBookable(Exception):
pass


class UserEventNbExceded(Exception):
pass


class UserEventNbExcededAdmin(Exception):
pass


class UserEventNbExcededUser(Exception):
pass


class ParticipantNbExceded(Exception):
pass
37 changes: 33 additions & 4 deletions backend/core/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,19 @@
import json

from flask import current_app
from sqlalchemy import func, or_
from sqlalchemy import func, or_, select, extract
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.orm import aliased

from .env import db

from core.exceptions import (
EventIsFull,
UserEventNbExceded,
NotBookable,
ParticipantNbExceded,
)


class GTEventsQuery:
def filter_properties(self, query, filters):
Expand Down Expand Up @@ -158,9 +165,31 @@ def massif(self):
def massif(cls):
return func.animations.get_secteur_name(cls.id)

def is_reservation_possible_for(self, nb_people):
def is_reservation_possible_for(self, nb_people, email):

if not self.bookable:
return False
raise NotBookable

# Test nombre de reservation par utilisateur
# Selection animations par utilisateur
from datetime import datetime

query = select(func.count(TReservations.id_reservation)).where(
TReservations.cancelled == False,
TReservations.email == email,
TReservations.confirmed == True,
extract("year", TReservations.meta_create_date) == datetime.today().year,
)
nb_reservation = db.session.scalar(query)

# On retranche un de façon a s'assurer que le nb d'animation
# ne sera pas suppérieur une fois l'animation ajoutée
if nb_reservation > current_app.config["NB_ANIM_MAX_PER_USER"] - 1:
raise UserEventNbExceded

if nb_people > current_app.config["NB_PARTICIPANTS_MAX_PER_ANIM_PER_USER"]:
raise ParticipantNbExceded

if not self.capacity:
return True
if self.sum_participants + nb_people <= self.capacity:
Expand All @@ -170,7 +199,7 @@ def is_reservation_possible_for(self, nb_people):
<= current_app.config["LISTE_ATTENTE_CAPACITY"]
):
return True
return False
raise EventIsFull


class GTCancellationReason(db.Model):
Expand Down
53 changes: 40 additions & 13 deletions backend/core/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from email_validator import validate_email, EmailNotValidError, EmailSyntaxError
from flask import jsonify, request, Blueprint, render_template, session, current_app

from sqlalchemy import select
from sqlalchemy import select, func, extract

from core.models import (
db,
Expand Down Expand Up @@ -33,7 +33,11 @@
get_mail_subject,
stringify,
)

from core.exceptions import (
UserEventNbExceded,
UserEventNbExcededAdmin,
UserEventNbExcededUser,
)

app_routes = Blueprint("app_routes", __name__)

Expand Down Expand Up @@ -257,10 +261,6 @@ def get_reservations():
)


class EventIsFull(Exception):
pass


class BodyParamValidationError(Exception):
pass

Expand All @@ -275,8 +275,12 @@ def _post_reservations_by_user(post_data):
f"Event with ID {reservation.id_event} not found"
)

if not event.is_reservation_possible_for(reservation.nb_participants):
raise EventIsFull
try:
event.is_reservation_possible_for(
reservation.nb_participants, reservation.email
)
except UserEventNbExceded:
raise UserEventNbExcededUser

reservation.token = generate_token()

Expand Down Expand Up @@ -308,8 +312,12 @@ def _post_reservations_by_admin(post_data):
f"Event with ID {reservation.id_event} not found"
)

if not event.is_reservation_possible_for(reservation.nb_participants):
raise EventIsFull
try:
event.is_reservation_possible_for(
reservation.nb_participants, reservation.email
)
except UserEventNbExceded:
raise UserEventNbExcededAdmin

if reservation.confirmed and reservation.liste_attente is None:
if not event.capacity:
Expand Down Expand Up @@ -390,9 +398,13 @@ def confirm_reservation():
return jsonify({"error": "Reservation already confirmed"}), 400

event = resa.event
if not event.is_reservation_possible_for(resa.nb_participants):
raise EventIsFull
elif not event.capacity:

try:
event.is_reservation_possible_for(resa.nb_participants, resa.email)
except UserEventNbExceded:
raise UserEventNbExcededUser

if not event.capacity:
resa.liste_attente = False
elif event.sum_participants + resa.nb_participants <= event.capacity:
resa.liste_attente = False
Expand All @@ -417,11 +429,26 @@ def update_reservation(reservation_id):
if not reservation:
return jsonify({"error": f"Reservation #{reservation_id} not found"}), 404

event = db.session.get(GTEvents, reservation.id_event)
old_nb_participants = reservation.nb_participants

if not event:
raise BodyParamValidationError(
f"Event with ID {reservation.id_event} not found"
)

post_data = request.get_json()
post_data["digitizer"] = session["user"]
validated_data = TReservationsUpdateSchema().load(post_data)

for k, v in validated_data.items():
setattr(reservation, k, v)
# On retranche l'ancien nombre de participants
if not event.is_reservation_possible_for(
reservation.nb_participants - old_nb_participants
):
raise EventIsFull

db.session.add(reservation)
db.session.commit()

Expand Down
48 changes: 47 additions & 1 deletion backend/test/test_api_admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from sqlalchemy import select
from core.models import GTEvents, TReservations
from core.models import TTokens
from core.routes import EventIsFull
from core.exceptions import EventIsFull
from core.env import db

from .utils import login
Expand Down Expand Up @@ -41,6 +41,21 @@
"num_departement": "48",
"confirmed": True,
}
TEST_RESERVATION_1_PERSONNE = {
"nom": "BLAIR",
"prenom": "Eric",
"commentaire": "saisie test",
"tel": "00 00 00 00 00 ",
"email": "[email protected]",
"nb_adultes": 1,
"nb_moins_6_ans": 0,
"nb_6_8_ans": 0,
"nb_9_12_ans": 0,
"nb_plus_12_ans": 0,
"num_departement": "48",
"confirmed": True,
}

TEST_BILAN = {
"commentaire": "test bilan",
"nb_6_8_ans": 1,
Expand Down Expand Up @@ -242,6 +257,37 @@ def test_post_export_and_cancel_one_reservation(self, events):
assert resa.cancelled == True
assert resa.cancel_by == "admin"

def test_post_limit_nb_animations(self, events):
login(self.client)
# Create reservation
event = db.session.scalars(
select(GTEvents)
.where(GTEvents.name == "Pytest bookable")
.order_by(GTEvents.id.desc())
).first()

data_resa = TEST_RESERVATION_1_PERSONNE
data_resa["id_event"] = event.id

nb_limit_per_user = current_app.config["NB_ANIM_MAX_PER_USER"]

# Création du nombre de reservation spécifié dans NB_ANIM_MAX_PER_USER
for loop in range(nb_limit_per_user):
resp = post_json(
self.client, url_for("app_routes.post_reservations"), data_resa
)
assert resp == 200

# Ajout de 1 reservation ce qui doit retourner une erreur
resp = post_json(
self.client, url_for("app_routes.post_reservations"), data_resa
)
assert resp.status_code == 422
assert (
json_of_response(resp)["error"]
== "La limite du nombre de réservation pour cet utilisateur est atteinte"
)

# def test_post_one_bilan(self):
# # POST
# event = GTEvents.query.limit(1).one()
Expand Down
Loading
Loading