1 # This file is part of Indico.
2 # Copyright (C) 2002 - 2015 European Organization for Nuclear Research (CERN).
4 # Indico is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License as
6 # published by the Free Software Foundation; either version 3 of the
7 # License, or (at your option) any later version.
9 # Indico is distributed in the hope that it will be useful, but
10 # WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 # General Public License for more details.
14 # You should have received a copy of the GNU General Public License
15 # along with Indico; if not, see <http://www.gnu.org/licenses/>.
17 from __future__
import unicode_literals
19 from datetime
import datetime
, time
20 from operator
import attrgetter
22 from indico
.core
.db
import db
23 from indico
.core
.db
.sqlalchemy
import PyIntEnum
24 from indico
.modules
.rb
.models
.blockings
import Blocking
25 from indico
.modules
.rb
.models
.reservation_occurrences
import ReservationOccurrence
26 from indico
.modules
.rb
.models
.reservations
import Reservation
27 from indico
.modules
.rb
.notifications
.blockings
import notify_request_response
28 from indico
.util
.string
import return_ascii
29 from indico
.util
.struct
.enum
import TitledIntEnum
32 class BlockedRoomState(TitledIntEnum
):
33 __titles__
= ['Pending', 'Accepted', 'Rejected']
39 class BlockedRoom(db
.Model
):
40 __tablename__
= 'blocked_rooms'
41 __table_args__
= {'schema': 'roombooking'}
43 State
= BlockedRoomState
# make it available here for convenience
50 PyIntEnum(BlockedRoomState
),
52 default
=BlockedRoomState
.pending
54 rejected_by
= db
.Column(
57 rejection_reason
= db
.Column(
60 blocking_id
= db
.Column(
62 db
.ForeignKey('roombooking.blockings.id'),
67 db
.ForeignKey('roombooking.rooms.id'),
74 return BlockedRoomState(self
.state
).title
77 def find_with_filters(cls
, filters
):
78 q
= cls
.find(_eager
=BlockedRoom
.blocking
, _join
=Blocking
)
79 if filters
.get('room_ids'):
80 q
= q
.filter(BlockedRoom
.room_id
.in_(filters
['room_ids']))
81 if filters
.get('start_date') and filters
.get('end_date'):
82 q
= q
.filter(Blocking
.start_date
<= filters
['end_date'],
83 Blocking
.end_date
>= filters
['start_date'])
84 if 'state' in filters
:
85 q
= q
.filter(BlockedRoom
.state
== filters
['state'])
88 def reject(self
, user
=None, reason
=None):
89 """Reject the room blocking."""
90 self
.state
= BlockedRoomState
.rejected
92 self
.rejection_reason
= reason
94 self
.rejected_by
= user
.full_name
95 notify_request_response(self
)
97 def approve(self
, notify_blocker
=True):
98 """Approve the room blocking, rejecting all colliding reservations/occurrences."""
99 self
.state
= BlockedRoomState
.accepted
101 # Get colliding reservations
102 start_dt
= datetime
.combine(self
.blocking
.start_date
, time())
103 end_dt
= datetime
.combine(self
.blocking
.end_date
, time(23, 59, 59))
105 reservation_criteria
= [
106 Reservation
.room_id
== self
.room_id
,
107 ~Reservation
.is_rejected
,
108 ~Reservation
.is_cancelled
111 # Whole reservations to reject
112 reservations
= Reservation
.find_all(
113 Reservation
.start_dt
>= start_dt
,
114 Reservation
.end_dt
<= end_dt
,
115 *reservation_criteria
118 # Single occurrences to reject
119 occurrences
= ReservationOccurrence
.find_all(
120 ReservationOccurrence
.start_dt
>= start_dt
,
121 ReservationOccurrence
.end_dt
<= end_dt
,
122 ReservationOccurrence
.is_valid
,
123 ~ReservationOccurrence
.reservation_id
.in_(map(attrgetter('id'), reservations
)) if reservations
else True,
124 *reservation_criteria
,
128 reason
= 'Conflict with blocking {}: {}'.format(self
.blocking
.id, self
.blocking
.reason
)
130 for reservation
in reservations
:
131 if self
.blocking
.can_be_overridden(reservation
.created_by_user
, reservation
.room
):
133 reservation
.reject(self
.blocking
.created_by_user
, reason
)
135 for occurrence
in occurrences
:
136 reservation
= occurrence
.reservation
137 if self
.blocking
.can_be_overridden(reservation
.created_by_user
, reservation
.room
):
139 occurrence
.reject(self
.blocking
.created_by_user
, reason
)
142 # We only need to notify the blocking creator if the blocked room wasn't approved yet.
143 # This is the case if it's a new blocking for a room managed by the creator
144 notify_request_response(self
)
148 return '<BlockedRoom({0}, {1}, {2})>'.format(