def test_align_dates(self): self.assertEqual(utils.align_date_to_day(datetime(2012, 1, 1, 0, 0), "down"), datetime(2012, 1, 1, 0, 0)) self.assertEqual( utils.align_date_to_day(datetime(2012, 1, 1, 0, 0), "up"), datetime(2012, 1, 1, 23, 59, 59, 999999) ) self.assertEqual(utils.align_date_to_day(datetime(2012, 1, 1, 0, 1), "down"), datetime(2012, 1, 1, 0, 0)) self.assertEqual( utils.align_date_to_day(datetime(2012, 1, 1, 0, 1), "up"), datetime(2012, 1, 1, 23, 59, 59, 999999) ) self.assertEqual( utils.align_date_to_day(datetime(2012, 1, 1, 23, 59, 59, 999999), "down"), datetime(2012, 1, 1, 0, 0) ) self.assertEqual( utils.align_date_to_day(datetime(2012, 1, 1, 23, 59, 59, 999999), "up"), datetime(2012, 1, 1, 23, 59, 59, 999999), ) self.assertEqual( utils.align_range_to_day(datetime(2012, 1, 2, 0, 0), datetime(2012, 1, 2, 0, 0)), (datetime(2012, 1, 2, 0, 0), datetime(2012, 1, 2, 23, 59, 59, 999999)), ) self.assertEqual( utils.align_range_to_day(datetime(2012, 1, 2, 0, 0), datetime(2012, 1, 3, 0, 0)), (datetime(2012, 1, 2, 0, 0), datetime(2012, 1, 3, 23, 59, 59, 999999)), )
def reserve(self, data): allocation = self.allocation(data['id']) approve_manually = allocation.approve_manually self.inject_missing_data(data, allocation) start, end = self.validate(data) quota = int(data.get('quota', 1)) # whole day allocations don't show the start / end time which is to # say the data arrives with 00:00 - 00:00. we align that to the day if allocation.whole_day: assert start == end start, end = utils.align_range_to_day(start, end) def reserve(): self.run_reserve( data=data, approve_manually=approve_manually, start=start, end=end, quota=quota ) utils.handle_action( action=reserve, success=self.redirect_to_your_reservations )
def reserve(self, data): allocation = self.allocation(data['id']) approve_manually = allocation.approve_manually self.inject_missing_data(data, allocation) start, end = self.validate(data) quota = int(data.get('quota', 1)) # whole day allocations don't show the start / end time which is to # say the data arrives with 00:00 - 00:00. we align that to the day if allocation.whole_day: if not allocation.partly_available: assert start == end if start == end: start, end = utils.align_range_to_day(start, end) def reserve(): self.run_reserve(data=data, approve_manually=approve_manually, start=start, end=end, quota=quota) utils.handle_action(action=reserve, success=self.redirect_to_your_reservations)
def test_align_dates(self): self.assertEqual( utils.align_date_to_day(datetime(2012, 1, 1, 0, 0), 'down'), datetime(2012, 1, 1, 0, 0)) self.assertEqual( utils.align_date_to_day(datetime(2012, 1, 1, 0, 0), 'up'), datetime(2012, 1, 1, 23, 59, 59, 999999)) self.assertEqual( utils.align_date_to_day(datetime(2012, 1, 1, 0, 1), 'down'), datetime(2012, 1, 1, 0, 0)) self.assertEqual( utils.align_date_to_day(datetime(2012, 1, 1, 0, 1), 'up'), datetime(2012, 1, 1, 23, 59, 59, 999999)) self.assertEqual( utils.align_date_to_day(datetime(2012, 1, 1, 23, 59, 59, 999999), 'down'), datetime(2012, 1, 1, 0, 0)) self.assertEqual( utils.align_date_to_day(datetime(2012, 1, 1, 23, 59, 59, 999999), 'up'), datetime(2012, 1, 1, 23, 59, 59, 999999)) self.assertEqual( utils.align_range_to_day(datetime(2012, 1, 2, 0, 0), datetime(2012, 1, 2, 0, 0)), (datetime(2012, 1, 2, 0, 0), datetime(2012, 1, 2, 23, 59, 59, 999999))) self.assertEqual( utils.align_range_to_day(datetime(2012, 1, 2, 0, 0), datetime(2012, 1, 3, 0, 0)), (datetime(2012, 1, 2, 0, 0), datetime(2012, 1, 3, 23, 59, 59, 999999)))
def move_allocation( self, master_id, new_start=None, new_end=None, group=None, new_quota=None, approve_manually=None, reservation_quota_limit=0, whole_day=None): assert master_id assert any([new_start and new_end, group, new_quota]) # Find allocation master = self.allocation_by_id(master_id) mirrors = self.allocation_mirrors_by_master(master) changing = [master] + mirrors ids = [c.id for c in changing] assert(group or master.group) # Simulate the new allocation new_start = new_start or master.start new_end = new_end or master.end if whole_day: new_start, new_end = utils.align_range_to_day(new_start, new_end) new = Allocation(start=new_start, end=new_end, raster=master.raster) # Ensure that the new span does not overlap an existing one existing_allocations = self.allocations_in_range(new.start, new.end) for existing in existing_allocations: if existing.id not in ids: raise OverlappingAllocationError(new.start, new.end, existing) for change in changing: if change.partly_available: # confirmed reservations for reservation in change.reserved_slots: if not new.contains(reservation.start, reservation.end): raise AffectedReservationError(reservation) # pending reservations if change.is_master: # (mirrors return the same values) for pending in change.pending_reservations.with_entities( Reservation.start, Reservation.end): if not new.contains(*pending): raise AffectedPendingReservationError(pending) else: # confirmed reservations if change.start != new.start or change.end != new.end: if len(change.reserved_slots): raise AffectedReservationError( change.reserved_slots[0] ) if change.is_master and \ change.pending_reservations.count(): raise AffectedPendingReservationError( change.pending_reservations[0] ) # the following attributes must be equal over all group members # (this still allows to use move_allocation to remove an allocation # from an existing group by specifiying the new group) for allocation in self.allocations_by_group(group or master.group): if approve_manually is not None: allocation.approve_manually = approve_manually if reservation_quota_limit is not None: allocation.reservation_quota_limit = reservation_quota_limit if new_quota is not None and allocation.is_master: self.change_quota(allocation, new_quota) for change in changing: change.start = new.start change.end = new.end change.group = group or master.group
def allocate(self, dates, raster=15, quota=None, partly_available=False, grouped=False, approve_manually=True, reservation_quota_limit=0, whole_day=False ): """Allocates a spot in the calendar. An allocation defines a timerange which can be reserved. No reservations can exist outside of existing allocations. In fact any reserved slot will link to an allocation. An allocation may be available as a whole (to reserve all or nothing). It may also be partly available which means reservations can be made for parts of the allocation. If an allocation is partly available a raster defines the granularity with which a reservation can be made (e.g. a raster of 15min will ensure that reservations are at least 15 minutes long and start either at :00, :15, :30 or :45) The reason for the raster is mainly to ensure that different reservations trying to reserve overlapping times need the same keys in the reserved_slots table, ensuring integrity at the database level. Allocations may have a quota, which determines how many times an allocation may be reserved. Quotas are enabled using a master-mirrors relationship. The master is the first allocation to be created. The mirrors copies of that allocation. See Scheduler.__doc__ """ dates = utils.pairs(dates) group = new_uuid() quota = quota or 1 # if the allocation is not partly available the raster is set to lowest # possible raster value raster = partly_available and raster or MIN_RASTER_VALUE # the whole day option results in the dates being aligned to # the beginning of the day / end of it -> not timezone aware! if whole_day: for ix, (start, end) in enumerate(dates): dates[ix] = utils.align_range_to_day(start, end) # Ensure that the list of dates contains no overlaps inside for start, end in dates: if utils.count_overlaps(dates, start, end) > 1: raise InvalidAllocationError # Make sure that this span does not overlap another master for start, end in dates: start, end = rasterize_span(start, end, raster) existing = self.allocations_in_range(start, end).first() if existing: raise OverlappingAllocationError(start, end, existing) # Write the master allocations allocations = [] for start, end in dates: allocation = Allocation(raster=raster) allocation.start = start allocation.end = end allocation.resource = self.uuid allocation.quota = quota allocation.mirror_of = self.uuid allocation.partly_available = partly_available allocation.approve_manually = approve_manually allocation.reservation_quota_limit = reservation_quota_limit if grouped: allocation.group = group else: allocation.group = new_uuid() allocations.append(allocation) Session.add_all(allocations) return allocations