def post(self): """Listing all availabilities for a given day, and for a given room if requested.""" # Get and validate inputs: args = self.parser.parse_args(strict=True) target_day = args["target_day"] room_code = args.get("room_code") floor = args.get("floor") # Get the rooms for which the computations must be done: db_session = new_session() if room_code: room = db_session.query(Room).get(room_code) if not room: raise NotFound(f"Unknown room code: {room_code}.") rooms = [room] elif floor is not None: rooms = db_session.query(Room).filter_by(floor=floor).all() else: rooms = db_session.query(Room).all() db_session.close() # Compute availabilities for all these rooms: availabilities = get_available_slots( target_day, room_codes=[r.code for r in rooms]) return availabilities, 200
def post(self): """Try to book a room""" # Get and validate inputs: args = self.post_parser.parse_args(strict=True) args = _validate_booking_inputs(args) # Check the availability of the room for the requested period: room_code = args["room_code"] start_datetime = args["start_datetime"] if not is_room_available(room_code, start_datetime, args["duration_in_hours"]): room_availability_info = get_available_slots( start_datetime.date(), room_codes=[room_code]) assert len(room_availability_info) == 1 return marshal(room_availability_info[0], room_availabilities_model), 409 # Book the room: new_booking = Booking( author=args["author"], start_datetime=start_datetime, duration=args["duration_in_hours"], room_code=room_code, ) db_session = new_session() db_session.add(new_booking) db_session.commit() db_session.close() return marshal(new_booking, booking_model), 201
def get(self, id: int): """Get a booking from its id.""" db_session = new_session() booking = db_session.query(Booking).get(id) db_session.close() if not booking: raise NotFound(f"This booking ID does not exist: {id}.") return booking, 200
def get(self, code: str): """Get the room identified by the code.""" db_session = new_session() room = db_session.query(Room).get(code) db_session.close() if not room: raise NotFound(f"The code {code} does not identify any room.") return room, 200
def delete(self, id: int): """Delete a booking identified by its id.""" db_session = new_session() # First check that this booking exists: booking = db_session.query(Booking).get(id) if not booking: raise NotFound(f"This booking ID does not exist: {id}.") # Then delete it: db_session.delete(booking) db_session.commit() db_session.close() return None, 204
def get(self): """List all bookings""" # Get the filters from inputs: filters = self.list_parser.parse_args(strict=True) # Build the filtered query: db_session = new_session() query = db_session.query(Booking) day_filter_value = filters.pop("day") if day_filter_value: query = query.filter( func.DATE(Booking.start_datetime) == day_filter_value) actual_filters = { key: value for key, value in filters.items() if value is not None } query = query.filter_by(**actual_filters) # Return all matching results: bookings = query.all() db_session.close() return bookings, 200
def get(self): """List all rooms""" # Get the input filters, if any: filters = self.parser.parse_args(strict=True) search_in_name = filters.get("search_in_name") floor = filters.get("floor") min_capacity = filters.get("min_capacity") # Build the query: db_session = new_session() query = db_session.query(Room) if search_in_name: query = query.filter(Room.name.ilike(f"%{search_in_name}%")) if floor is not None: query = query.filter_by(floor=floor) if min_capacity: query = query.filter(Room.capacity >= min_capacity) # Return all matching results: res = query.all() db_session.close() return res, 200
def _validate_booking_inputs(input_args: Dict[str, Any]) -> Dict[str, Any]: # Extract values (all are required, no error expected): output_args = input_args.copy() room_code: str = input_args["room_code"] start_datetime: dt.datetime = input_args["start_datetime"] duration: int = input_args["duration_in_hours"] # Check that the room_code refers to an existing room: db_session = new_session() room = db_session.query(Room).get(room_code) if not room: raise NotFound( f"No room bearing the code {room_code}. Please provide a valid one." ) # Check that the start datetime is an hour start: if start_datetime.minute != 0 or start_datetime.second != 0: raise UnprocessableEntity( "The start_datetime must not contain minutes nor seconds: one can only book rooms for entire hours." ) # Make the start datetime localized in the same time zone as the room: local_tz = timezone(room.building.tz_name) if start_datetime.tzinfo is None: output_args["start_datetime"] = local_tz.localize(start_datetime) else: output_args["start_datetime"] = start_datetime.astimezone(local_tz) # Check that the booking duration does not lead to the next day: if not 0 < duration <= 25: raise UnprocessableEntity( "No booking duration is allowed to exceed a day. " "The parameter duration_in_hours must be a positive number less or equal to 24." ) db_session.close() return output_args
def is_room_available(room_code: str, start_datetime: dt.datetime, duration_in_hours: int) -> bool: """ Returns True if the room is available during the whole requested period, False otherwise. """ # Get all bookings related to this room: db_session = new_session() daily_room_bookings = db_session.query(Booking) \ .filter_by(room_code=room_code) \ .filter(func.DATE(Booking.start_datetime) == start_datetime.date()) \ .all() # Detect any booked period overlapping the requested one: end_datetime = start_datetime + dt.timedelta(hours=duration_in_hours) for booking in daily_room_bookings: if start_datetime <= booking.start_datetime < end_datetime or \ start_datetime < booking.start_datetime + dt.timedelta(hours=booking.duration) <= end_datetime: return False db_session.close() # If none was found, the room is available: return True
def get_available_slots(requested_day: dt.date, *, room_codes: List[str]) -> List[RoomFreeSlots]: """ Return the list of bookable periods during the requested day for the target rooms. """ # Get all bookings related to these rooms: db_session = new_session() requested_day_bookings = db_session.query(Booking) \ .filter(Booking.room_code.in_(room_codes), func.DATE(Booking.start_datetime) == requested_day) \ .order_by(Booking.room_code, Booking.start_datetime) \ .all() # Reorganize the results per room: bookings_per_room: Dict[str, List[Booking]] = {} for booking in requested_day_bookings: bookings_per_room.setdefault(booking.room_code, []).append(booking) # Compute the remaining free slots... free_slots = [] for code in room_codes: # ... for the rooms that are free for the whole day (for which no booking exists): if code not in bookings_per_room: room = db_session.query(Room).get(code) local_tz = timezone(room.building.tz_name) requested_day_start = local_tz.localize( dt.datetime.combine(requested_day, dt.time())) room_free_slots = [{ "start_datetime": requested_day_start, "duration_in_hours": 24 }] free_slots.append({ "room_code": code, "free_slots": room_free_slots }) continue # ... for each room having at least one booking: room_bookings = bookings_per_room[code] room_free_slots = [] # ... First, detect if there is a free slot before the first booking: first_booking = room_bookings[0] first_booking_start: dt.datetime = first_booking.start_datetime local_tz = timezone(first_booking.room.building.tz_name) requested_day_start = local_tz.localize( dt.datetime.combine(requested_day, dt.time())) if first_booking_start.hour != 0: room_free_slots.append({ "start_datetime": requested_day_start, "duration_in_hours": first_booking_start.hour }) # ... Second, detect all available slots between two bookings: for i, booking in enumerate(requested_day_bookings[:-1]): current_booking_end_datetime = booking.start_datetime + dt.timedelta( hours=booking.duration) next_booking_start_datetime = requested_day_bookings[ i + 1].start_datetime if current_booking_end_datetime != next_booking_start_datetime: new_free_slot_timedelta = next_booking_start_datetime - current_booking_end_datetime new_free_slot_duration_in_hours = int( new_free_slot_timedelta.total_seconds() // 3600) room_free_slots.append({ "start_datetime": current_booking_end_datetime, "duration_in_hours": new_free_slot_duration_in_hours, }) # ... Finally, detect if there is a free slot at the end of the day, after the last booking: last_booking = requested_day_bookings[-1] last_booking_end = last_booking.start_datetime + dt.timedelta( hours=last_booking.duration) if last_booking_end.hour != 0: room_free_slots.append({ "start_datetime": last_booking_end, "duration_in_hours": 24 - last_booking_end.hour }) # ... Save the results: free_slots.append({"room_code": code, "free_slots": room_free_slots}) db_session.close() return free_slots