def check_policy_to_create_outage(outage: ScheduledOutage): # Outages may not have a start time that is earlier than the end time. if outage.start >= outage.end: return "Outage start time (" + format_datetime( outage.start ) + ") must be before the end time (" + format_datetime( outage.end) + ")." # The user may not create, move, or resize an outage to coincide with another user's reservation. coincident_events = Reservation.objects.filter( **outage.outage_item_filter).filter(cancelled=False, missed=False, shortened=False) # Exclude events for which the following is true: # The event starts and ends before the time-window, and... # The event starts and ends after the time-window. coincident_events = coincident_events.exclude(start__lt=outage.start, end__lte=outage.start) coincident_events = coincident_events.exclude(start__gte=outage.end, end__gt=outage.end) if coincident_events.count() > 0: return "Your scheduled outage coincides with a reservation that already exists. Please choose a different time." # No policy issues! The outage can be created... return None
def save(self, commit=True): instance = super(TaskForm, self).save(commit=False) action = self.cleaned_data['action'] description = self.cleaned_data['description'] instance.problem_category = self.cleaned_data['problem_category'] if action == 'create': instance.problem_description = description instance.urgency = Task.Urgency.HIGH if self.cleaned_data[ 'force_shutdown'] or self.cleaned_data[ 'safety_hazard'] else Task.Urgency.NORMAL instance.creator = self.user if action == 'update': instance.status = Task.Status.WORK_IN_PROGRESS instance.last_updated = timezone.now() instance.last_updated_by = self.user if description: preface = 'On ' + format_datetime(timezone.now( )) + ' ' + self.user.get_full_name() + ' updated this task:\n' if instance.progress_description is None: instance.progress_description = preface + description else: instance.progress_description += '\n\n' + preface + description instance.progress_description = instance.progress_description.strip( ) if action == 'resolve': instance.status = Task.Status.COMPLETE instance.resolution_time = timezone.now() instance.resolver = self.user if 'resolution_category' in self.cleaned_data: instance.resolution_category = self.cleaned_data[ 'resolution_category'] if 'description' in self.cleaned_data: if instance.resolution_description: preface = 'On ' + format_datetime( timezone.now()) + ' ' + self.user.get_full_name( ) + ' updated the resolution information:\n' instance.resolution_description = ( instance.resolution_description + '\n\n' + preface + self.cleaned_data['description']).strip() else: instance.resolution_description = self.cleaned_data[ 'description'] if instance.first_response_time is None: instance.first_response_time = timezone.now() if instance.first_responder is None: instance.first_responder = self.user if action in ('update', 'resolve'): if instance.first_response_time is None: instance.first_response_time = timezone.now() if instance.first_responder is None: instance.first_responder = self.user return super(TaskForm, self).save(commit=commit)
def check_policy_to_cancel_reservation(reservation, user_cancelling_reservation): """ Checks the reservation deletion policy. If all checks pass the function returns an HTTP "OK" response. Otherwise, the function returns an HTTP "Bad Request" with an error message. """ # Users may only cancel reservations that they own. # Staff may break this rule. if (reservation.user != user_cancelling_reservation ) and not user_cancelling_reservation.is_staff: return HttpResponseBadRequest( "You may not cancel reservations that you do not own.") # Users may not cancel reservations that have already ended. # Staff may break this rule. if reservation.end < timezone.now( ) and not user_cancelling_reservation.is_staff: return HttpResponseBadRequest( "You may not cancel reservations that have already ended.") if reservation.cancelled: return HttpResponseBadRequest( "This reservation has already been cancelled by " + str(reservation.cancelled_by) + " at " + format_datetime(reservation.cancellation_time) + ".") if reservation.missed: return HttpResponseBadRequest( "This reservation was missed and cannot be modified.") return HttpResponse()
def email_usage_reminders(request): projects_to_exclude = request.GET.getlist("projects_to_exclude[]") busy_users = AreaAccessRecord.objects.filter( end=None, staff_charge=None).exclude(project__id__in=projects_to_exclude) busy_tools = UsageEvent.objects.filter(end=None).exclude( project__id__in=projects_to_exclude) facility_name = get_customization('facility_name') if facility_name == '': facility_name = "Facility" # Make lists of all the things a user is logged in to. # We don't want to send 3 separate emails if a user is logged into three things. # Just send one email for all the things! aggregate = {} for access_record in busy_users: key = str(access_record.customer) aggregate[key] = { 'email': access_record.customer.email, 'first_name': access_record.customer.first_name, 'resources_in_use': [str(access_record.area)], } for usage_event in busy_tools: key = str(usage_event.operator) if key in aggregate: aggregate[key]['resources_in_use'].append(usage_event.tool.name) else: aggregate[key] = { 'email': usage_event.operator.email, 'first_name': usage_event.operator.first_name, 'resources_in_use': [usage_event.tool.name], } user_office_email = get_customization('user_office_email_address') message = get_media_file_contents('usage_reminder_email.html') if message: subject = f"{facility_name} usage" for user in aggregate.values(): rendered_message = Template(message).render(Context({'user': user})) send_mail(subject, '', user_office_email, [user['email']], html_message=rendered_message) message = get_media_file_contents('staff_charge_reminder_email.html') if message: busy_staff = StaffCharge.objects.filter(end=None) for staff_charge in busy_staff: subject = "Active staff charge since " + format_datetime( staff_charge.start) rendered_message = Template(message).render( Context({'staff_charge': staff_charge})) staff_charge.staff_member.email_user(subject, rendered_message, user_office_email) return HttpResponse()
def reservation_details(request, reservation_id): reservation = get_object_or_404(Reservation, id=reservation_id) if reservation.cancelled: error_message = 'This reservation was cancelled by {0} at {1}.'.format( reservation.cancelled_by, format_datetime(reservation.cancellation_time)) return HttpResponseNotFound(error_message) return render(request, 'calendar/reservation_details.html', {'reservation': reservation})
def save(self, commit=True): instance = super(SafetyIssueUpdateForm, self).save(commit=False) progress_type = 'resolved' if self.cleaned_data['resolved'] else 'updated' if progress_type == 'resolved': instance.resolution = self.cleaned_data['update'] instance.resolution_time = timezone.now() instance.resolver = self.user if progress_type == 'updated' and self.cleaned_data['update']: progress = 'On ' + format_datetime(timezone.now()) + ' ' + self.user.get_full_name() + ' updated this issue:\n' + self.cleaned_data['update'] if instance.progress: instance.progress += '\n\n' + progress else: instance.progress = progress return super(SafetyIssueUpdateForm, self).save(commit=commit)
def check_policy_to_cancel_reservation(reservation, user_cancelling_reservation): """ Checks the reservation deletion policy. If all checks pass the function returns an HTTP "OK" response. Otherwise, the function returns an HTTP "Bad Request" with an error message. """ # Users may only cancel reservations that they own. # Staff may break this rule. if (reservation.user != user_cancelling_reservation ) and not user_cancelling_reservation.is_staff: return HttpResponseBadRequest( "You may not cancel reservations that you do not own.") # Users may not cancel reservations that have already ended. # Staff may break this rule. if reservation.end < timezone.now( ) and not user_cancelling_reservation.is_staff: return HttpResponseBadRequest( "You may not cancel reservations that have already ended.") # Users may not cancel ongoing area reservations when they are currently logged in that area # Staff may break this rule. if reservation.area and reservation.area.requires_reservation and reservation.start < timezone.now( ) < reservation.end and AreaAccessRecord.objects.filter( end=None, staff_charge=None, customer=reservation.user, area=reservation.area ) and not user_cancelling_reservation.is_staff: return HttpResponseBadRequest( "You may not cancel an area reservation while logged in that area." ) if reservation.cancelled: return HttpResponseBadRequest( "This reservation has already been cancelled by " + str(reservation.cancelled_by) + " at " + format_datetime(reservation.cancellation_time) + ".") if reservation.missed: return HttpResponseBadRequest( "This reservation was missed and cannot be modified.") return HttpResponse()
def save(self, commit=True): instance = super(SafetyIssueUpdateForm, self).save(commit=False) progress_type = "resolved" if self.cleaned_data[ "resolved"] else "updated" if progress_type == "resolved": instance.resolution = self.cleaned_data["update"] instance.resolution_time = timezone.now() instance.resolver = self.user if progress_type == "updated" and self.cleaned_data["update"]: progress = ("On " + format_datetime(timezone.now()) + " " + self.user.get_full_name() + " updated this issue:\n" + self.cleaned_data["update"]) if instance.progress: instance.progress += "\n\n" + progress else: instance.progress = progress return super(SafetyIssueUpdateForm, self).save(commit=commit)
def reservation_details(request, reservation_id): reservation = get_object_or_404(Reservation, id=reservation_id) if reservation.cancelled: error_message = 'This reservation was cancelled by {0} at {1}.'.format( reservation.cancelled_by, format_datetime(reservation.cancellation_time)) return HttpResponseNotFound(error_message) reservation_project_can_be_changed = ( request.user.is_staff or request.user == reservation.user ) and reservation.has_not_ended and reservation.has_not_started and reservation.user.active_project_count( ) > 1 return render( request, 'calendar/reservation_details.html', { 'reservation': reservation, 'reservation_project_can_be_changed': reservation_project_can_be_changed })
def check_policy_to_create_outage(outage, request): # Outages may not have a start time that is earlier than the end time. if outage.start >= outage.end: return "Outage start time (" + format_datetime(outage.start) + ") must be before the end time (" + format_datetime(outage.end) + ")." # The user may not create, move, or resize an outage to coincide with another user's reservation. coincident_events = Reservation.objects.filter(tool=outage.tool, cancelled=False, missed=False, shortened=False) # Exclude events for which the following is true: # The event starts and ends before the time-window, and... # The event starts and ends after the time-window. coincident_events = coincident_events.exclude(start__lt=outage.start, end__lte=outage.start) coincident_events = coincident_events.exclude(start__gte=outage.end, end__gt=outage.end) if coincident_events.count() > 0: return "Your scheduled outage coincides with a reservation that already exists. Please choose a different time." # prevent staff from creating outages on tools in different cores active_core_id = request.session.get("active_core_id") if str(active_core_id) != "0" and str(active_core_id) != "None": if str(outage.tool.core_id.id) not in str(active_core_id) and outage.tool not in user.qualifications.all() and not user.is_superuser: msg = "Your core is not the same as the core of " + str(outage.tool.core_id.name) + " to which the " + str(outage.tool.name) + " belongs. You cannot create an outage for this tool." return HttpResponseBadRequest(msg) # No policy issues! The outage can be created... return None
def check_policy_to_save_reservation(cancelled_reservation, new_reservation, user, explicit_policy_override): """ Check the reservation creation policy and return a list of policy problems """ # The function will check all policies. Policy problems are placed in the policy_problems list. overridable is True if the policy problems can be overridden by a staff member. policy_problems = [] overridable = False # Reservations may not have a start time that is earlier than the end time. if new_reservation.start >= new_reservation.end: policy_problems.append("Reservation start time (" + format_datetime(new_reservation.start) + ") must be before the end time (" + format_datetime(new_reservation.end) + ").") # The user may not create, move, or resize a reservation to coincide with another user's reservation. coincident_events = Reservation.objects.filter(tool=new_reservation.tool, cancelled=False, missed=False, shortened=False) # Exclude the reservation we're cancelling in order to create a new one: if cancelled_reservation and cancelled_reservation.id: coincident_events = coincident_events.exclude( id=cancelled_reservation.id) # Exclude events for which the following is true: # The event starts and ends before the time-window, and... # The event starts and ends after the time-window. coincident_events = coincident_events.exclude( start__lt=new_reservation.start, end__lte=new_reservation.start) coincident_events = coincident_events.exclude( start__gte=new_reservation.end, end__gt=new_reservation.end) if coincident_events.count() > 0: policy_problems.append( "Your reservation coincides with another reservation that already exists. Please choose a different time." ) # The user may not create, move, or resize a reservation to coincide with a scheduled outage. coincident_events = ScheduledOutage.objects.filter( Q(tool=new_reservation.tool) | Q(resource__fully_dependent_tools__in=[new_reservation.tool])) # Exclude events for which the following is true: # The event starts and ends before the time-window, and... # The event starts and ends after the time-window. coincident_events = coincident_events.exclude( start__lt=new_reservation.start, end__lte=new_reservation.start) coincident_events = coincident_events.exclude( start__gte=new_reservation.end, end__gt=new_reservation.end) if coincident_events.count() > 0: policy_problems.append( "Your reservation coincides with a scheduled outage. Please choose a different time." ) # Reservations that have been cancelled may not be changed. if new_reservation.cancelled: policy_problems.append( "This reservation has already been cancelled by " + str(new_reservation.cancelled_by) + " at " + format_datetime(new_reservation.cancellation_time) + ".") # The user must belong to at least one active project to make a reservation. if new_reservation.user.active_project_count() < 1: if new_reservation.user == user: policy_problems.append( "You do not belong to any active projects. Thus, you may not create any reservations." ) else: policy_problems.append( str(new_reservation.user) + " does not belong to any active projects and cannot have reservations." ) # The user must associate their reservation with a project they belong to. if new_reservation.project and new_reservation.project not in new_reservation.user.active_projects( ): if new_reservation.user == user: policy_problems.append( "You do not belong to the project associated with this reservation." ) else: policy_problems.append( str(new_reservation.user) + " does not belong to the project named " + str(new_reservation.project) + ".") # If the user is a staff member or there's an explicit policy override then the policy check is finished. if user.is_staff or explicit_policy_override: return policy_problems, overridable # If there are no blocking policy conflicts at this point, the rest of the policies can be overridden. if not policy_problems: overridable = True # The user must complete NEMO training to create reservations. # Staff may break this rule. # An explicit policy override allows this rule to be broken. if user.training_required: policy_problems.append( "You are blocked from making reservations for all tools in the facility. Please complete the rules tutorial in order to create new reservations." ) # Users may only change their own reservations. # Staff may break this rule. # An explicit policy override allows this rule to be broken. if new_reservation.user != user: policy_problems.append( "You may not change reservations that you do not own.") # The user may not create or move a reservation to have a start time that is earlier than the current time. # Staff may break this rule. # An explicit policy override allows this rule to be broken. if new_reservation.start < timezone.now(): policy_problems.append("Reservation start time (" + format_datetime(new_reservation.start) + ") is earlier than the current time (" + format_datetime(timezone.now()) + ").") # The user may not move or resize a reservation to have an end time that is earlier than the current time. # Staff may break this rule. # An explicit policy override allows this rule to be broken. if new_reservation.end < timezone.now(): policy_problems.append("Reservation end time (" + format_datetime(new_reservation.end) + ") is earlier than the current time (" + format_datetime(timezone.now()) + ").") # The user must be qualified on the tool in question in order to create, move, or resize a reservation. # Staff may break this rule. # An explicit policy override allows this rule to be broken. if new_reservation.tool not in user.qualifications.all(): policy_problems.append( "You are not qualified to use this tool. Creating, moving, and resizing reservations is forbidden." ) # The reservation start time may not exceed the tool's reservation horizon. # Staff may break this rule. # An explicit policy override allows this rule to be broken. if new_reservation.tool.reservation_horizon is not None: reservation_horizon = timedelta( days=new_reservation.tool.reservation_horizon) if new_reservation.start > timezone.now() + reservation_horizon: policy_problems.append( "You may not create reservations further than " + str(reservation_horizon.days) + " days from now for this tool.") # Calculate the duration of the reservation: duration = new_reservation.end - new_reservation.start # The reservation must be at least as long as the minimum block time for this tool. # Staff may break this rule. # An explicit policy override allows this rule to be broken. if new_reservation.tool.minimum_usage_block_time: minimum_block_time = timedelta( minutes=new_reservation.tool.minimum_usage_block_time) if duration < minimum_block_time: policy_problems.append( "Your reservation has a duration of " + str(int(duration.total_seconds() / 60)) + " minutes. This tool requires a minimum reservation duration of " + str(int(minimum_block_time.total_seconds() / 60)) + " minutes.") # The reservation may not exceed the maximum block time for this tool. # Staff may break this rule. # An explicit policy override allows this rule to be broken. if new_reservation.tool.maximum_usage_block_time: maximum_block_time = timedelta( minutes=new_reservation.tool.maximum_usage_block_time) if duration > maximum_block_time: policy_problems.append( "Your reservation has a duration of " + str(int(duration.total_seconds() / 60)) + " minutes. Reservations for this tool may not exceed " + str(int(maximum_block_time.total_seconds() / 60)) + " minutes.") # If there is a limit on number of reservations per user per day then verify that the user has not exceeded it. # Staff may break this rule. # An explicit policy override allows this rule to be broken. if new_reservation.tool.maximum_reservations_per_day: start_of_day = new_reservation.start start_of_day = start_of_day.replace(hour=0, minute=0, second=0, microsecond=0) end_of_day = start_of_day + timedelta(days=1) reservations_for_that_day = Reservation.objects.filter( cancelled=False, shortened=False, start__gte=start_of_day, end__lte=end_of_day, user=user, tool=new_reservation.tool) # Exclude any reservation that is being cancelled. if cancelled_reservation and cancelled_reservation.id: reservations_for_that_day = reservations_for_that_day.exclude( id=cancelled_reservation.id) if reservations_for_that_day.count( ) >= new_reservation.tool.maximum_reservations_per_day: policy_problems.append( "You may only have " + str(new_reservation.tool.maximum_reservations_per_day) + " reservations for this tool per day. Missed reservations are included when counting the number of reservations per day." ) # A minimum amount of time between reservations for the same user & same tool can be enforced. # Staff may break this rule. # An explicit policy override allows this rule to be broken. if new_reservation.tool.minimum_time_between_reservations: buffer_time = timedelta( minutes=new_reservation.tool.minimum_time_between_reservations) must_end_before = new_reservation.start - buffer_time too_close = Reservation.objects.filter(cancelled=False, shortened=False, user=user, end__gt=must_end_before, start__lt=new_reservation.start, tool=new_reservation.tool) if cancelled_reservation and cancelled_reservation.id: too_close = too_close.exclude(id=cancelled_reservation.id) if too_close.exists(): policy_problems.append( "Separate reservations for this tool that belong to you must be at least " + str(new_reservation.tool.minimum_time_between_reservations) + " minutes apart from each other. The proposed reservation ends too close to another reservation." ) must_start_after = new_reservation.end + buffer_time too_close = Reservation.objects.filter(cancelled=False, shortened=False, user=user, start__lt=must_start_after, end__gt=new_reservation.start, tool=new_reservation.tool) if cancelled_reservation and cancelled_reservation.id: too_close = too_close.exclude(id=cancelled_reservation.id) if too_close.exists(): policy_problems.append( "Separate reservations for this tool that belong to you must be at least " + str(new_reservation.tool.minimum_time_between_reservations) + " minutes apart from each other. The proposed reservation begins too close to another reservation." ) # Check that the user is not exceeding the maximum amount of time they may reserve in the future. # Staff may break this rule. # An explicit policy override allows this rule to be broken. if new_reservation.tool.maximum_future_reservation_time: reservations_after_now = Reservation.objects.filter( cancelled=False, user=user, tool=new_reservation.tool, start__gte=timezone.now()) if cancelled_reservation and cancelled_reservation.id: reservations_after_now = reservations_after_now.exclude( id=cancelled_reservation.id) amount_reserved_in_the_future = new_reservation.duration() for r in reservations_after_now: amount_reserved_in_the_future += r.duration() if amount_reserved_in_the_future.total_seconds( ) / 60 > new_reservation.tool.maximum_future_reservation_time: policy_problems.append( "You may only reserve up to " + str(new_reservation.tool.maximum_future_reservation_time) + " minutes of time on this tool, starting from the current time onward." ) # Return the list of all policies that are not met. return policy_problems, overridable
def check_policy_to_cancel_reservation(user_cancelling_reservation: User, reservation_to_cancel: Reservation, new_reservation: Optional[Reservation] = None): """ Checks the reservation deletion policy. If all checks pass the function returns an HTTP "OK" response. Otherwise, the function returns an HTTP "Bad Request" with an error message. """ move = new_reservation and new_reservation.start != reservation_to_cancel.start resize = new_reservation and new_reservation.start == reservation_to_cancel.start action = 'move' if move else 'resize' if resize else 'cancel' # Users may only cancel reservations that they own. # Staff may break this rule. if (reservation_to_cancel.user != user_cancelling_reservation) and not user_cancelling_reservation.is_staff: return HttpResponseBadRequest(f"You may not {action} reservations that you do not own.") # Users may not cancel reservations that have already ended. # Staff may break this rule. if reservation_to_cancel.end < timezone.now() and not user_cancelling_reservation.is_staff: return HttpResponseBadRequest(f"You may not {action} reservations that have already ended.") # Users may not cancel ongoing area reservations when they are currently logged in that area (unless they are extending it) # Staff may break this rule. if reservation_to_cancel.area and reservation_to_cancel.area.requires_reservation and not resize and reservation_to_cancel.start < timezone.now() < reservation_to_cancel.end and AreaAccessRecord.objects.filter(end=None, staff_charge=None, customer=reservation_to_cancel.user, area=reservation_to_cancel.area) and not user_cancelling_reservation.is_staff: if move: return HttpResponseBadRequest("You may only resize an area reservation while logged in that area.") else: return HttpResponseBadRequest("You may not cancel an area reservation while logged in that area.") if reservation_to_cancel.cancelled: return HttpResponseBadRequest("This reservation has already been cancelled by " + str(reservation_to_cancel.cancelled_by) + " on " + format_datetime(reservation_to_cancel.cancellation_time) + ".") if reservation_to_cancel.missed: return HttpResponseBadRequest("This reservation was missed and cannot be modified.") return HttpResponse()
def check_policy_to_save_reservation( cancelled_reservation: Optional[Reservation], new_reservation: Reservation, user_creating_reservation: User, explicit_policy_override: bool): """ Check the reservation creation policy and return a list of policy problems if any. """ user = new_reservation.user facility_name = get_customization('facility_name') # The function will check all policies. Policy problems are placed in the policy_problems list. overridable is True if the policy problems can be overridden by a staff member. policy_problems = [] overridable = False item_type = new_reservation.reservation_item_type # Reservations may not have a start time that is earlier than the end time. if new_reservation.start >= new_reservation.end: policy_problems.append("Reservation start time (" + format_datetime(new_reservation.start) + ") must be before the end time (" + format_datetime(new_reservation.end) + ").") check_coincident_item_reservation_policy(cancelled_reservation, new_reservation, user_creating_reservation, policy_problems) # Reservations that have been cancelled may not be changed. if new_reservation.cancelled: policy_problems.append( "This reservation has already been cancelled by " + str(new_reservation.cancelled_by) + " at " + format_datetime(new_reservation.cancellation_time) + ".") # The user must belong to at least one active project to make a reservation. if user.active_project_count() < 1: if user == user_creating_reservation: policy_problems.append( "You do not belong to any active projects. Thus, you may not create any reservations." ) else: policy_problems.append( str(user) + " does not belong to any active projects and cannot have reservations." ) # The user must associate their reservation with a project they belong to. if new_reservation.project and new_reservation.project not in user.active_projects( ): if user == user_creating_reservation: policy_problems.append( "You do not belong to the project associated with this reservation." ) else: policy_problems.append( str(user) + " does not belong to the project named " + str(new_reservation.project) + ".") # If the user is a staff member or there's an explicit policy override then the policy check is finished. if user.is_staff or explicit_policy_override: return policy_problems, overridable # If there are no blocking policy conflicts at this point, the rest of the policies can be overridden. if not policy_problems: overridable = True # Some tool reservations require a prior area reservation # Staff may break this rule. # An explicit policy override allows this rule to be broken. if item_type == ReservationItemType.TOOL: if new_reservation.tool.requires_area_reservation(): area: Area = new_reservation.tool.requires_area_access # Check that a reservation for the area has been made and contains the start time if not Reservation.objects.filter( missed=False, cancelled=False, shortened=False, user=user, area=area, start__lte=new_reservation.start, end__gt=new_reservation.start).exists(): if user == user_creating_reservation: policy_problems.append( f"This tool requires a {area} reservation. Please make a reservation in the {area} prior to reserving this tool." ) else: policy_problems.append( f"This tool requires a {area} reservation. Please make sure to also create a reservation in the {area} or {str(user)} will not be able to enter the area." ) # The user must complete training to create reservations. # Staff may break this rule. # An explicit policy override allows this rule to be broken. if user.training_required: if user == user_creating_reservation: policy_problems.append( f"You are blocked from making reservations in the {facility_name}. Please complete the {facility_name} rules tutorial in order to create new reservations." ) else: policy_problems.append( f"{str(user)} is blocked from making reservations in the {facility_name}. The user needs to complete the {facility_name} rules tutorial in order to create new reservations." ) # Users may only change their own reservations. # Staff may break this rule. # An explicit policy override allows this rule to be broken. if cancelled_reservation and user != user_creating_reservation: policy_problems.append( "You may not change reservations that you do not own.") # The user may not create or move a reservation to have a start time that is earlier than the current time. # Staff may break this rule. # An explicit policy override allows this rule to be broken. if new_reservation.start < timezone.now(): policy_problems.append("Reservation start time (" + format_datetime(new_reservation.start) + ") is earlier than the current time (" + format_datetime(timezone.now()) + ").") # The user may not move or resize a reservation to have an end time that is earlier than the current time. # Staff may break this rule. # An explicit policy override allows this rule to be broken. if new_reservation.end < timezone.now(): policy_problems.append("Reservation end time (" + format_datetime(new_reservation.end) + ") is earlier than the current time (" + format_datetime(timezone.now()) + ").") # The user must be qualified on the tool in question in order to create, move, or resize a reservation. # Staff may break this rule. # An explicit policy override allows this rule to be broken. if new_reservation.tool and new_reservation.tool not in user.qualifications.all( ): if user == user_creating_reservation: policy_problems.append( "You are not qualified to use this tool. Creating, moving, and resizing reservations is forbidden." ) else: policy_problems.append( f"{str(user)} is not qualified to use this tool. Creating, moving, and resizing reservations is forbidden." ) # The user must be authorized on the area in question at the start and end times of the reservation in order to create, move, or resize a reservation. # Staff may break this rule. # An explicit policy override allows this rule to be broken. if item_type == ReservationItemType.AREA: user_access_levels = user.accessible_access_levels_for_area( new_reservation.area) if not any([ access_level.accessible_at(new_reservation.start) for access_level in user_access_levels ]) or not any([ access_level.accessible_at(new_reservation.end) for access_level in user_access_levels ]): details = f" (times allowed in this area are: {','.join([access.get_schedule_display_with_times() for access in user_access_levels])})" if user_access_levels else '' if user == user_creating_reservation: policy_problems.append( f"You are not authorized to access this area at this time{details}. Creating, moving, and resizing reservations is forbidden." ) else: policy_problems.append( f"{str(user)} is not authorized to access this area at this time{details}. Creating, moving, and resizing reservations is forbidden." ) # The reservation start time may not exceed the item's reservation horizon. # Staff may break this rule. # An explicit policy override allows this rule to be broken. item = new_reservation.reservation_item if item.reservation_horizon is not None: reservation_horizon = timedelta(days=item.reservation_horizon) if new_reservation.start > timezone.now() + reservation_horizon: policy_problems.append( "You may not create reservations further than " + str(reservation_horizon.days) + f" days from now for this {item_type.value}.") # Check item policy rules item_policy_problems = [] if should_enforce_policy(new_reservation): item_policy_problems = check_policy_rules_for_item( cancelled_reservation, new_reservation, user_creating_reservation) # Return the list of all policies that are not met. return policy_problems + item_policy_problems, overridable
def email_reservation_reminders(request): # Exit early if the reservation reminder email template has not been customized for the organization yet. good_message = get_media_file_contents('reservation_reminder_email.html') problem_message = get_media_file_contents('reservation_warning_email.html') if not good_message or not problem_message: return HttpResponseNotFound( 'The reservation reminder email template has not been customized for your organization yet. Please visit the NEMO customizable_key_values page to upload a template, then reservation reminder email notifications can be sent.' ) # Find all reservations in the next day #preparation_time = 120 These were sending an email for each reservation 2 hrs in advance. I'm not using them #tolerance = 5 earliest_start = timezone.now() latest_start = timezone.now() + timedelta(hours=24) upcoming_reservations = Reservation.objects.filter( cancelled=False, start__gt=earliest_start, start__lt=latest_start) # Email a reminder to each user with an upcoming reservation. goodAggregate = {} problemAggregate = {} for reservation in upcoming_reservations: key = str(reservation.user) if reservation.tool.operational and not reservation.tool.problematic( ) and reservation.tool.all_resources_available(): if key in goodAggregate: goodAggregate[key]['Tools'].append( reservation.tool.name + " starting " + format_datetime(reservation.start)) else: goodAggregate[key] = { 'email': reservation.user.email, 'first_name': reservation.user.first_name, 'Tools': [ reservation.tool.name + " starting " + format_datetime(reservation.start) ], } else: if key in problemAggregate: problemAggregate[key]['Tools'].append( reservation.tool.name + " starting " + format_datetime(reservation.start)) else: problemAggregate[key] = { 'email': reservation.user.email, 'first_name': reservation.user.first_name, 'Tools': [ reservation.tool.name + " starting " + format_datetime(reservation.start) ], } user_office_email = get_customization('user_office_email_address') if good_message: subject = "Upcoming PRISM Cleanroom Reservations" for user in goodAggregate.values(): rendered_message = Template(good_message).render( Context({ 'user': user, 'template_color': bootstrap_primary_color('success') })) send_mail(subject, '', user_office_email, [user['email']], html_message=rendered_message) if problem_message: subject = "Problem With Upcoming PRISM Cleanroom Reservations" for user in problemAggregate.values(): rendered_message = Template(problem_message).render( Context({ 'user': user, 'template_color': bootstrap_primary_color('danger') })) send_mail(subject, '', user_office_email, [user['email']], html_message=rendered_message) return HttpResponse()
def check_policy_to_cancel_reservation(reservation, user, request): """ Checks the reservation deletion policy. If all checks pass the function returns an HTTP "OK" response. Otherwise, the function returns an HTTP "Bad Request" with an error message. """ # Users may only cancel reservations that they own. # Staff may break this rule. if (reservation.user != user) and not user.is_staff: return HttpResponseBadRequest("You may not cancel reservations that you do not own.") # Users may not cancel reservations that have already ended. # Staff may break this rule. if reservation.end.replace(tzinfo=None) < timezone.now().replace(tzinfo=None) and not user.is_staff: return HttpResponseBadRequest("You may not cancel reservations that have already ended.") if reservation.cancelled: return HttpResponseBadRequest("This reservation has already been cancelled by " + str(reservation.cancelled_by) + " at " + format_datetime(reservation.cancellation_time) + ".") if reservation.missed: return HttpResponseBadRequest("This reservation was missed and cannot be modified.") # Staff may only cancel reservations for tools in their core active_core_id = request.session.get("active_core_id") if str(active_core_id) != "0" and str(active_core_id) != "None": if str(reservation.tool.core_id.id) not in str(active_core_id) and reservation.tool not in user.qualifications.all() and not user.is_superuser: msg = "Your core is not the same as the core of " + str(reservation.tool.core_id.name) + " to which the " + str(reservation.tool.name) + " belongs. You cannot cancel a reservation for this tool." return HttpResponseBadRequest(msg) return HttpResponse()
def check_policy_to_save_reservation(cancelled_reservation, new_reservation, user, explicit_policy_override): """ Check the reservation creation policy and return a list of policy problems """ # The function will check all policies. Policy problems are placed in the policy_problems list. overridable is True if the policy problems can be overridden by a staff member. policy_problems = [] overridable = False # Reservations may not have a start time that is earlier than the end time. if new_reservation.start >= new_reservation.end: policy_problems.append("Reservation start time (" + format_datetime(new_reservation.start) + ") must be before the end time (" + format_datetime(new_reservation.end) + ").") # The user may not create, move, or resize a reservation to coincide with another user's reservation. coincident_events = Reservation.objects.filter(tool=new_reservation.tool, cancelled=False, missed=False, shortened=False) # Exclude the reservation we're cancelling in order to create a new one: if cancelled_reservation and cancelled_reservation.id: coincident_events = coincident_events.exclude(id=cancelled_reservation.id) # Exclude events for which the following is true: # The event starts and ends before the time-window, and... # The event starts and ends after the time-window. coincident_events = coincident_events.exclude(start__lt=new_reservation.start, end__lte=new_reservation.start) coincident_events = coincident_events.exclude(start__gte=new_reservation.end, end__gt=new_reservation.end) if coincident_events.count() > 0: policy_problems.append("Your reservation coincides with another reservation that already exists. Please choose a different time.") # The user may not create, move, or resize a reservation to coincide with a scheduled outage. coincident_events = ScheduledOutage.objects.filter(Q(tool=new_reservation.tool) | Q(resource__fully_dependent_tools__in=[new_reservation.tool])) # Exclude events for which the following is true: # The event starts and ends before the time-window, and... # The event starts and ends after the time-window. coincident_events = coincident_events.exclude(start__lt=new_reservation.start, end__lte=new_reservation.start) coincident_events = coincident_events.exclude(start__gte=new_reservation.end, end__gt=new_reservation.end) if coincident_events.count() > 0: policy_problems.append("Your reservation coincides with a scheduled outage. Please choose a different time.") # Reservations that have been cancelled may not be changed. if new_reservation.cancelled: policy_problems.append("This reservation has already been cancelled by " + str(new_reservation.cancelled_by) + " at " + format_datetime(new_reservation.cancellation_time) + ".") # The user must belong to at least one active project to make a reservation. if new_reservation.user.active_project_count() < 1: if new_reservation.user == user: policy_problems.append("You do not belong to any active projects. Thus, you may not create any reservations.") else: policy_problems.append(str(new_reservation.user) + " does not belong to any active projects and cannot have reservations.") # The user must associate their reservation with a project they belong to. if new_reservation.project and new_reservation.project not in new_reservation.user.active_projects(): if new_reservation.user == user: policy_problems.append("You do not belong to the project associated with this reservation.") else: policy_problems.append(str(new_reservation.user) + " does not belong to the project named " + str(new_reservation.project) + ".") # If the user is a staff member or there's an explicit policy override then the policy check is finished. if user.is_staff or explicit_policy_override: return policy_problems, overridable # If there are no blocking policy conflicts at this point, the rest of the policies can be overridden. if not policy_problems: overridable = True # The user must complete NEMO training to create reservations. # Staff may break this rule. # An explicit policy override allows this rule to be broken. if user.training_required: policy_problems.append("You are blocked from making reservations for all tools in the NanoFab. Please complete the NanoFab rules tutorial in order to create new reservations.") # Users may only change their own reservations. # Staff may break this rule. # An explicit policy override allows this rule to be broken. if new_reservation.user != user: policy_problems.append("You may not change reservations that you do not own.") # The user may not create or move a reservation to have a start time that is earlier than the current time. # Staff may break this rule. # An explicit policy override allows this rule to be broken. if new_reservation.start < timezone.now(): policy_problems.append("Reservation start time (" + format_datetime(new_reservation.start) + ") is earlier than the current time (" + format_datetime(timezone.now()) + ").") # The user may not move or resize a reservation to have an end time that is earlier than the current time. # Staff may break this rule. # An explicit policy override allows this rule to be broken. if new_reservation.end < timezone.now(): policy_problems.append("Reservation end time (" + format_datetime(new_reservation.end) + ") is earlier than the current time (" + format_datetime(timezone.now()) + ").") # The user must be qualified on the tool in question in order to create, move, or resize a reservation. # Staff may break this rule. # An explicit policy override allows this rule to be broken. if new_reservation.tool not in user.qualifications.all(): policy_problems.append("You are not qualified to use this tool. Creating, moving, and resizing reservations is forbidden.") # The reservation start time may not exceed the tool's reservation horizon. # Staff may break this rule. # An explicit policy override allows this rule to be broken. if new_reservation.tool.reservation_horizon is not None: reservation_horizon = timedelta(days=new_reservation.tool.reservation_horizon) if new_reservation.start > timezone.now() + reservation_horizon: policy_problems.append("You may not create reservations further than " + str(reservation_horizon.days) + " days from now for this tool.") # Check tool policy rules tool_policy_problems = [] if new_reservation.tool.should_enforce_policy(new_reservation): tool_policy_problems = check_policy_rules_for_tool(cancelled_reservation, new_reservation, user) # Return the list of all policies that are not met. return policy_problems + tool_policy_problems, overridable
def __issue_command(self, interlock: Interlock_model, command_type: Interlock_model.State): interlocks_enabled = getattr(settings, 'INTERLOCKS_ENABLED', False) if not interlocks_enabled or not interlock.card.enabled: interlock.most_recent_reply = "Interlock interface mocked out because settings.INTERLOCKS_ENABLED = False or interlock card is disabled. Interlock last set on " + format_datetime( timezone.now()) + "." interlock.state = command_type interlock.save() return True state = Interlock_model.State.UNKNOWN error_message = '' # try to send the command to the interlock try: state = self._send_command(interlock, command_type) except InterlockError as error: interlocks_logger.error(error) error_message = error.message except Exception as error: interlocks_logger.error(error) error_message = str(error) # save interlock state interlock.state = state interlock.most_recent_reply = Interlock.__create_reply_message( command_type, state, error_message) interlock.save() # log some useful information if interlock.state == interlock.State.UNKNOWN: interlocks_logger.error( f"Interlock {interlock.id} is in an unknown state. {interlock.most_recent_reply}" ) elif interlock.state == interlock.State.LOCKED: interlocks_logger.debug( f"Interlock {interlock.id} locked successfully at {format_datetime(timezone.now())}" ) elif interlock.state == interlock.State.UNLOCKED: interlocks_logger.debug( f"Interlock {interlock.id} unlocked successfully at {format_datetime(timezone.now())}" ) # If the command type equals the current state then the command worked which will return true: return interlock.state == command_type