def modify(self, data, user): """Modifies an existing reservation. :param data: A dict containing the booking data, usually from a :class:`ModifyBookingForm` instance :param user: The :class:`.User` who modifies the booking. """ populate_fields = ('start_dt', 'end_dt', 'repeat_frequency', 'repeat_interval', 'booked_for_user', 'booking_reason') # fields affecting occurrences occurrence_fields = {'start_dt', 'end_dt', 'repeat_frequency', 'repeat_interval'} # fields where date and time are compared separately date_time_fields = {'start_dt', 'end_dt'} # fields for the repetition repetition_fields = {'repeat_frequency', 'repeat_interval'} # pretty names for logging field_names = { 'start_dt/date': u"start date", 'end_dt/date': u"end date", 'start_dt/time': u"start time", 'end_dt/time': u"end time", 'repetition': u"booking type", 'booked_for_user': u"'Booked for' user", 'booking_reason': u"booking reason", } self.room.check_advance_days(data['end_dt'].date(), user) self.room.check_bookable_hours(data['start_dt'].time(), data['end_dt'].time(), user) changes = {} update_occurrences = False old_repetition = self.repetition for field in populate_fields: if field not in data: continue old = getattr(self, field) new = data[field] converter = unicode if old != new: # Booked for user updates the (redundant) name if field == 'booked_for_user': old = self.booked_for_name new = self.booked_for_name = data[field].full_name # Apply the change setattr(self, field, data[field]) # If any occurrence-related field changed we need to recreate the occurrences if field in occurrence_fields: update_occurrences = True # Record change for history entry if field in date_time_fields: # The date/time fields create separate entries for the date and time parts if old.date() != new.date(): changes[field + '/date'] = {'old': old.date(), 'new': new.date(), 'converter': format_date} if old.time() != new.time(): changes[field + '/time'] = {'old': old.time(), 'new': new.time(), 'converter': format_time} elif field in repetition_fields: # Repetition needs special handling since it consists of two fields but they are tied together # We simply update it whenever we encounter such a change; after the last change we end up with # the correct change data changes['repetition'] = {'old': old_repetition, 'new': self.repetition, 'converter': lambda x: RepeatMapping.get_message(*x)} else: changes[field] = {'old': old, 'new': new, 'converter': converter} if not changes: return False # Create a verbose log entry for the modification log = [u'Booking modified'] for field, change in changes.iteritems(): field_title = field_names.get(field, field) converter = change['converter'] old = to_unicode(converter(change['old'])) new = to_unicode(converter(change['new'])) if not old: log.append(u"The {} was set to '{}'".format(field_title, new)) elif not new: log.append(u"The {} was cleared".format(field_title)) else: log.append(u"The {} was changed from '{}' to '{}'".format(field_title, old, new)) self.edit_logs.append(ReservationEditLog(user_name=user.full_name, info=log)) # Recreate all occurrences if necessary if update_occurrences: cols = [col.name for col in ReservationOccurrence.__table__.columns if not col.primary_key and col.name not in {'start_dt', 'end_dt'}] old_occurrences = {occ.date: occ for occ in self.occurrences} self.occurrences.delete(synchronize_session='fetch') self.create_occurrences(True, user) db.session.flush() # Restore rejection data etc. for recreated occurrences for occurrence in self.occurrences: old_occurrence = old_occurrences.get(occurrence.date) # Copy data from old occurrence UNLESS the new one is invalid (e.g. because of collisions) # Otherwise we'd end up with valid occurrences ignoring collisions! if old_occurrence and occurrence.is_valid: for col in cols: setattr(occurrence, col, getattr(old_occurrence, col)) # Don't cause new notifications for the entire booking in case of daily repetition if self.repeat_frequency == RepeatFrequency.DAY and all(occ.notification_sent for occ in old_occurrences.itervalues()): for occurrence in self.occurrences: occurrence.notification_sent = True # Sanity check so we don't end up with an "empty" booking if not any(occ.is_valid for occ in self.occurrences): raise NoReportError(_(u'Reservation has no valid occurrences')) notify_modification(self, changes) return True
def modify(self, data, user): """Modifies an existing reservation. :param data: A dict containing the booking data, usually from a :class:`ModifyBookingForm` instance :param user: The :class:`.User` who modifies the booking. """ populate_fields = ( "start_dt", "end_dt", "repeat_frequency", "repeat_interval", "booked_for_user", "contact_email", "contact_phone", "booking_reason", "used_equipment", "needs_assistance", "uses_vc", "needs_vc_assistance", ) # fields affecting occurrences occurrence_fields = {"start_dt", "end_dt", "repeat_frequency", "repeat_interval"} # fields where date and time are compared separately date_time_fields = {"start_dt", "end_dt"} # fields for the repetition repetition_fields = {"repeat_frequency", "repeat_interval"} # pretty names for logging field_names = { "start_dt/date": "start date", "end_dt/date": "end date", "start_dt/time": "start time", "end_dt/time": "end time", "repetition": "booking type", "booked_for_user": "******", "contact_email": "contact email", "contact_phone": "contact phone number", "booking_reason": "booking reason", "used_equipment": "list of equipment", "needs_assistance": "option 'General Assistance'", "uses_vc": "option 'Uses Videoconference'", "needs_vc_assistance": "option 'Videoconference Setup Assistance'", } self.room.check_advance_days(data["end_dt"].date(), user) self.room.check_bookable_hours(data["start_dt"].time(), data["end_dt"].time(), user) changes = {} update_occurrences = False old_repetition = self.repetition for field in populate_fields: if field not in data: continue old = getattr(self, field) new = data[field] converter = unicode if field == "used_equipment": # Dynamic relationship old = sorted(old.all()) converter = lambda x: u", ".join(x.name for x in x) if old != new: # Booked for user updates the (redundant) name if field == "booked_for_user": old = self.booked_for_name new = self.booked_for_name = data[field].full_name # Apply the change setattr(self, field, data[field]) # If any occurrence-related field changed we need to recreate the occurrences if field in occurrence_fields: update_occurrences = True # Record change for history entry if field in date_time_fields: # The date/time fields create separate entries for the date and time parts if old.date() != new.date(): changes[field + "/date"] = {"old": old.date(), "new": new.date(), "converter": format_date} if old.time() != new.time(): changes[field + "/time"] = {"old": old.time(), "new": new.time(), "converter": format_time} elif field in repetition_fields: # Repetition needs special handling since it consists of two fields but they are tied together # We simply update it whenever we encounter such a change; after the last change we end up with # the correct change data changes["repetition"] = { "old": old_repetition, "new": self.repetition, "converter": lambda x: RepeatMapping.get_message(*x), } else: changes[field] = {"old": old, "new": new, "converter": converter} if not changes: return False # Create a verbose log entry for the modification log = [u"Booking modified"] for field, change in changes.iteritems(): field_title = field_names.get(field, field) converter = change["converter"] old = converter(change["old"]) new = converter(change["new"]) if not old: log.append(u"The {} was set to '{}'".format(field_title, new)) elif not new: log.append(u"The {} was cleared".format(field_title)) else: log.append(u"The {} was changed from '{}' to '{}'".format(field_title, old, new)) self.edit_logs.append(ReservationEditLog(user_name=user.full_name, info=log)) # Recreate all occurrences if necessary if update_occurrences: cols = [ col.name for col in ReservationOccurrence.__table__.columns if not col.primary_key and col.name not in {"start_dt", "end_dt"} ] old_occurrences = {occ.date: occ for occ in self.occurrences} self.occurrences.delete(synchronize_session="fetch") self.create_occurrences(True, user) db.session.flush() # Restore rejection data etc. for recreated occurrences for occurrence in self.occurrences: old_occurrence = old_occurrences.get(occurrence.date) # Copy data from old occurrence UNLESS the new one is invalid (e.g. because of collisions) # Otherwise we'd end up with valid occurrences ignoring collisions! if old_occurrence and occurrence.is_valid: for col in cols: setattr(occurrence, col, getattr(old_occurrence, col)) # Don't cause new notifications for the entire booking in case of daily repetition if self.repeat_frequency == RepeatFrequency.DAY and all( occ.notification_sent for occ in old_occurrences.itervalues() ): for occurrence in self.occurrences: occurrence.notification_sent = True # Sanity check so we don't end up with an "empty" booking if not any(occ.is_valid for occ in self.occurrences): raise NoReportError(_("Reservation has no valid occurrences")) notify_modification(self, changes) return True