diff --git a/HISTORY.rst b/HISTORY.rst index 3612297..d3386f7 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -1,6 +1,9 @@ Changelog --------- +- Reservations added to the same session may not be duplicated anymore. + [href] + - Errors raised during reservation now have a 'reservation' attribute. [href] diff --git a/libres/db/scheduler.py b/libres/db/scheduler.py index 1998a3d..4dc9cbf 100644 --- a/libres/db/scheduler.py +++ b/libres/db/scheduler.py @@ -985,6 +985,19 @@ def new_reservations_by_dates(dates): if not reservations: raise errors.InvalidReservationError + # have a very simple overlap check for reservations, it's not important + # that this catches *all* possible problems - that's being handled + # by the reservation slots - but it should stop us from adding the same + # reservation twice on a single session + if session_id: + found = self.queries.reservations_by_session(session_id) + found = found.with_entities(Reservation.target, Reservation.start) + found = set(found.all()) + + for reservation in reservations: + if (reservation.target, reservation.start) in found: + raise errors.OverlappingReservationError + for reservation in reservations: self.session.add(reservation) diff --git a/libres/modules/errors.py b/libres/modules/errors.py index 207642b..bfbd875 100644 --- a/libres/modules/errors.py +++ b/libres/modules/errors.py @@ -90,6 +90,10 @@ def __init__(self, start, end, existing): self.existing = existing +class OverlappingReservationError(LibresError): + pass + + class AffectedReservationError(LibresError): def __init__(self, existing): diff --git a/libres/tests/test_reservation.py b/libres/tests/test_reservation.py index a3854e8..666cb1c 100644 --- a/libres/tests/test_reservation.py +++ b/libres/tests/test_reservation.py @@ -2,7 +2,9 @@ from datetime import datetime, timedelta from libres.db.models import Reservation +from libres.modules.errors import OverlappingReservationError from sedate import standardize_date +from uuid import uuid4 def test_reservation_title(): @@ -53,3 +55,36 @@ def test_group_reservation_timespans(scheduler): assert timespans[1].start == standardize_date(dates[1][0], 'Europe/Zurich') assert timespans[1].end == standardize_date(dates[1][1], 'Europe/Zurich')\ - timedelta(microseconds=1) + + +def test_overlapping_reservations(scheduler): + start = datetime(2015, 2, 5, 15) + end = datetime(2015, 2, 5, 16) + + scheduler.allocate(dates=(start, end)) + + # overlapping reservations are only prohibited on a per-session base + scheduler.reserve( + email='test@example.org', + dates=(start, end), + session_id=uuid4() + ) + scheduler.reserve( + email='test@example.org', + dates=(start, end), + session_id=uuid4() + ) + + session_id = uuid4() + scheduler.reserve( + email='test@example.org', + dates=(start, end), + session_id=session_id + ) + + with pytest.raises(OverlappingReservationError): + scheduler.reserve( + email='test@example.org', + dates=(start, end), + session_id=session_id + ) diff --git a/libres/tests/test_scheduler.py b/libres/tests/test_scheduler.py index 3e8cba5..bbe57fe 100644 --- a/libres/tests/test_scheduler.py +++ b/libres/tests/test_scheduler.py @@ -366,21 +366,24 @@ def test_reserve_single_token_per_session(scheduler): session_id = new_uuid() - start = datetime(2011, 1, 1, 15) - end = datetime(2011, 1, 1, 16) - - scheduler.allocate((start, end), quota=3) + a1, a2 = scheduler.allocate( + dates=( + datetime(2011, 1, 1, 15), datetime(2011, 1, 1, 16), + datetime(2011, 1, 2, 15), datetime(2011, 1, 2, 16) + ), + quota=1 + ) token1 = scheduler.reserve( email=u'test@example.org', - dates=(start, end), + dates=(a1.start, a1.end), session_id=session_id, single_token_per_session=True ) token2 = scheduler.reserve( email=u'test@example.org', - dates=(start, end), + dates=(a2.start, a2.end), session_id=session_id, single_token_per_session=True ) @@ -391,14 +394,14 @@ def test_reserve_single_token_per_session(scheduler): token3 = scheduler.reserve( email=u'test@example.org', - dates=(start, end), + dates=(a1.start, a1.end), session_id=session_id, single_token_per_session=True ) token4 = scheduler.reserve( email=u'test@example.org', - dates=(start, end), + dates=(a2.start, a2.end), session_id=session_id, single_token_per_session=True )