def clean(self): cleaned_data = super().clean() tool = cleaned_data.get("tool") tool_usage_question_name = cleaned_data.get("tool_usage_question") if tool and tool_usage_question_name: error = None if tool.post_usage_questions: post_usage_form = DynamicForm(tool.post_usage_questions, tool.id) tool_question = post_usage_form.filter_questions( lambda x: (isinstance(x, PostUsageNumberFieldQuestion) or isinstance(x, PostUsageFloatFieldQuestion)) and x.name == tool_usage_question_name ) if not tool_question: candidates = [ question.name for question in post_usage_form.filter_questions( lambda x: isinstance(x, PostUsageNumberFieldQuestion) or isinstance(x, PostUsageFloatFieldQuestion) ) ] error = "The tool has no post usage question of type Number or Float with this name." if candidates: error += f" Valid question names are: {', '.join(candidates)}" else: error = "The tool does not have any post usage questions." if error: self.add_error("tool_usage_question", error) return cleaned_data
def disable_tool(request, tool_id): if not settings.ALLOW_CONDITIONAL_URLS: return HttpResponseBadRequest('Tool control is only available on campus.') tool = get_object_or_404(Tool, id=tool_id) if tool.get_current_usage_event() is None: return HttpResponse() current_usage_event = tool.get_current_usage_event() downtime = timedelta(minutes=quiet_int(request.POST.get('downtime'))) response = check_policy_to_disable_tool(tool, request.user, downtime) if response.status_code != HTTPStatus.OK: return response try: current_reservation = Reservation.objects.get(start__lt=timezone.now(), end__gt=timezone.now(), cancelled=False, missed=False, shortened=False, user=current_usage_event.user, tool=tool) # Staff are exempt from mandatory reservation shortening when tool usage is complete. if request.user.is_staff is False: # Shorten the user's reservation to the current time because they're done using the tool. new_reservation = deepcopy(current_reservation) new_reservation.id = None new_reservation.pk = None new_reservation.end = timezone.now() + downtime new_reservation.save() current_reservation.shortened = True current_reservation.descendant = new_reservation current_reservation.save() except Reservation.DoesNotExist: pass # All policy checks passed so disable the tool for the user. if tool.interlock and not tool.interlock.lock(): error_message = f"The interlock command for the {tool} failed. The error message returned: {tool.interlock.most_recent_reply}" logger.error(error_message) return HttpResponseServerError(error_message) # End the current usage event for the tool current_usage_event.end = timezone.now() + downtime # Collect post-usage questions dynamic_form = DynamicForm(tool.post_usage_questions) current_usage_event.run_data = dynamic_form.extract(request) dynamic_form.charge_for_consumable(current_usage_event.user, current_usage_event.operator, current_usage_event.project, current_usage_event.run_data) current_usage_event.save() if request.user.charging_staff_time(): existing_staff_charge = request.user.get_staff_charge() if existing_staff_charge.customer == current_usage_event.user and existing_staff_charge.project == current_usage_event.project: response = render(request, 'staff_charges/reminder.html', {'tool': tool}) return response
def tool_information(request, tool_id, user_id, back): tool = Tool.objects.get(id=tool_id, visible=True) customer = User.objects.get(id=user_id) dictionary = { 'customer': customer, 'tool': tool, 'rendered_configuration_html': tool.configuration_widget(customer), 'post_usage_questions': DynamicForm(tool.post_usage_questions).render(), 'back': back, } try: current_reservation = Reservation.objects.get(start__lt=timezone.now(), end__gt=timezone.now(), cancelled=False, missed=False, shortened=False, user=customer, tool=tool) remaining_reservation_duration = int( (current_reservation.end - timezone.now()).total_seconds() / 60) # We don't need to bother telling the user their reservation will be shortened if there's less than two minutes left. # Staff are exempt from reservation shortening. if remaining_reservation_duration > 2 and not customer.is_staff: dictionary[ 'remaining_reservation_duration'] = remaining_reservation_duration except Reservation.DoesNotExist: pass return render(request, 'kiosk/tool_information.html', dictionary)
def _post_usage_preview(self, obj): form_validity_div = '<div id="form_validity"></div>' if obj.post_usage_questions else '' return format_html( '<div class="post_usage_preview">{}{}</div><div class="help post_usage_preview_help">Save form to preview post usage questions</div>' .format( DynamicForm(obj.post_usage_questions).render(), form_validity_div))
def tool_status(request, tool_id): """ Gets the current status of the tool (that is, whether it is currently in use or not). """ tool = get_object_or_404(Tool, id=tool_id, visible=True) dictionary = { 'tool': tool, 'tool_rate': rates.rate_class.get_tool_rate(tool), 'task_categories': TaskCategory.objects.filter(stage=TaskCategory.Stage.INITIAL_ASSESSMENT), 'rendered_configuration_html': tool.configuration_widget(request.user), 'mobile': request.device == 'mobile', 'task_statuses': TaskStatus.objects.all(), 'post_usage_questions': DynamicForm(tool.post_usage_questions).render(), 'configs': get_tool_full_config_history(tool), } try: current_reservation = Reservation.objects.get(start__lt=timezone.now(), end__gt=timezone.now(), cancelled=False, missed=False, shortened=False, user=request.user, tool=tool) if request.user == current_reservation.user: dictionary['time_left'] = current_reservation.end except Reservation.DoesNotExist: pass # Staff need the user list to be able to qualify users for the tool. if request.user.is_staff: dictionary['users'] = User.objects.filter(is_active=True) return render(request, 'tool_control/tool_status.html', dictionary)
def disable_tool(request, tool_id): if not settings.ALLOW_CONDITIONAL_URLS: return HttpResponseBadRequest( "Tool control is only available on campus.") tool = get_object_or_404(Tool, id=tool_id) if tool.get_current_usage_event() is None: return HttpResponse() downtime = timedelta(minutes=quiet_int(request.POST.get("downtime"))) bypass_interlock = request.POST.get("bypass", 'False') == 'True' response = check_policy_to_disable_tool(tool, request.user, downtime) if response.status_code != HTTPStatus.OK: return response # All policy checks passed so disable the tool for the user. if tool.interlock and not tool.interlock.lock(): if bypass_interlock and interlock_bypass_allowed(request.user): pass else: return interlock_error("Disable", request.user) # Shorten the user's tool reservation since we are now done using the tool shorten_reservation(user=request.user, item=tool, new_end=timezone.now() + downtime) # End the current usage event for the tool current_usage_event = tool.get_current_usage_event() current_usage_event.end = timezone.now() + downtime # Collect post-usage questions dynamic_form = DynamicForm(tool.post_usage_questions, tool.id) try: current_usage_event.run_data = dynamic_form.extract(request) except RequiredUnansweredQuestionsException as e: if request.user.is_staff and request.user != current_usage_event.operator and current_usage_event.user != request.user: # if a staff is forcing somebody off the tool and there are required questions, send an email and proceed current_usage_event.run_data = e.run_data email_managers_required_questions_disable_tool( current_usage_event.operator, request.user, tool, e.questions) else: return HttpResponseBadRequest(str(e)) dynamic_form.charge_for_consumables(current_usage_event.user, current_usage_event.operator, current_usage_event.project, current_usage_event.run_data, request) dynamic_form.update_counters(current_usage_event.run_data) current_usage_event.save() user: User = request.user if user.charging_staff_time(): existing_staff_charge = user.get_staff_charge() if (existing_staff_charge.customer == current_usage_event.user and existing_staff_charge.project == current_usage_event.project): response = render(request, "staff_charges/reminder.html", {"tool": tool}) return response
def disable_tool(request, tool_id): if not settings.ALLOW_CONDITIONAL_URLS: return HttpResponseBadRequest( 'Tool control is only available on campus.') tool = get_object_or_404(Tool, id=tool_id) if tool.get_current_usage_event() is None: return HttpResponse() downtime = timedelta(minutes=quiet_int(request.POST.get('downtime'))) response = check_policy_to_disable_tool(tool, request.user, downtime) if response.status_code != HTTPStatus.OK: return response # Shorten the user's tool reservation since we are now done using the tool shorten_reservation(user=request.user, item=tool, new_end=timezone.now() + downtime) # All policy checks passed so disable the tool for the user. if tool.interlock and not tool.interlock.lock(): error_message = f"The interlock command for the {tool} failed. The error message returned: {tool.interlock.most_recent_reply}" tool_control_logger.error(error_message) return HttpResponseServerError(error_message) # End the current usage event for the tool current_usage_event = tool.get_current_usage_event() current_usage_event.end = timezone.now() + downtime # Collect post-usage questions dynamic_form = DynamicForm(tool.post_usage_questions) current_usage_event.run_data = dynamic_form.extract(request) dynamic_form.charge_for_consumables(current_usage_event.user, current_usage_event.operator, current_usage_event.project, current_usage_event.run_data) current_usage_event.save() if request.user.charging_staff_time(): existing_staff_charge = request.user.get_staff_charge() if existing_staff_charge.customer == current_usage_event.user and existing_staff_charge.project == current_usage_event.project: response = render(request, 'staff_charges/reminder.html', {'tool': tool}) return response
def disable_tool(request): logger = getLogger(__name__) tool = Tool.objects.get(id=request.POST['tool_id']) customer = User.objects.get(id=request.POST['customer_id']) downtime = timedelta(minutes=quiet_int(request.POST.get('downtime'))) response = check_policy_to_disable_tool(tool, customer, downtime) if response.status_code != HTTPStatus.OK: dictionary = { 'message': response.content, 'delay': 10, } return render(request, 'kiosk/acknowledgement.html', dictionary) try: current_reservation = Reservation.objects.get(start__lt=timezone.now(), end__gt=timezone.now(), cancelled=False, missed=False, shortened=False, user=customer, tool=tool) # Staff are exempt from mandatory reservation shortening when tool usage is complete. if customer.is_staff is False: # Shorten the user's reservation to the current time because they're done using the tool. new_reservation = deepcopy(current_reservation) new_reservation.id = None new_reservation.pk = None new_reservation.end = timezone.now() new_reservation.save() current_reservation.shortened = True current_reservation.descendant = new_reservation current_reservation.save() except Reservation.DoesNotExist: pass # All policy checks passed so disable the tool for the user. if tool.interlock and not tool.interlock.lock(): logger.error( "The interlock command for this tool failed. The error message returned: " + str(tool.interlock.most_recent_reply)) raise Exception( "The interlock command for this tool failed. The error message returned: " + str(tool.interlock.most_recent_reply)) # End the current usage event for the tool and save it. current_usage_event = tool.get_current_usage_event() current_usage_event.end = timezone.now() + downtime # Collect post-usage questions current_usage_event.run_data = DynamicForm( tool.post_usage_questions).extract(request) current_usage_event.save() dictionary = { 'message': 'You are no longer using the {}'.format(tool), 'badge_number': customer.badge_number, } return render(request, 'kiosk/acknowledgement.html', dictionary)
def _post_usage_preview(self, obj): if obj.id: form_validity_div = '<div id="form_validity"></div>' if obj.post_usage_questions else "" return mark_safe( '<div class="questions_preview">{}{}</div><div class="help questions_preview_help">Save form to preview post usage questions</div>' .format( DynamicForm(obj.post_usage_questions).render( "tool_usage_group_question", obj.id), form_validity_div))
def clean(self): cleaned_data = super().clean() reservation_questions = cleaned_data.get("questions") tool_reservations = cleaned_data.get("tool_reservations") only_tools = cleaned_data.get("only_for_tools") area_reservations = cleaned_data.get("area_reservations") only_areas = cleaned_data.get("only_for_areas") if not tool_reservations and not area_reservations: self.add_error( "tool_reservations", "Reservation questions have to apply to tool and/or area reservations" ) self.add_error( "area_reservations", "Reservation questions have to apply to tool and/or area reservations" ) if not tool_reservations and only_tools: self.add_error( "tool_reservations", "You cannot restrict tools these questions apply to without enabling it for tools" ) if not area_reservations and only_areas: self.add_error( "area_reservations", "You cannot restrict areas these questions apply to without enabling it for areas" ) # Validate reservation_questions JSON format if reservation_questions: try: loads(reservation_questions) except ValueError: self.add_error("questions", "This field needs to be a valid JSON string") try: dynamic_form = DynamicForm(reservation_questions) dynamic_form.validate("reservation_group_question", self.instance.id) except KeyError as e: self.add_error("questions", f"{e} property is required") except Exception: error_info = sys.exc_info() self.add_error( "questions", error_info[0].__name__ + ": " + str(error_info[1]))
def clean(self): cleaned_data = super().clean() parent_tool = cleaned_data.get("parent_tool") category = cleaned_data.get("_category") location = cleaned_data.get("_location") phone_number = cleaned_data.get("_phone_number") primary_owner = cleaned_data.get("_primary_owner") image = cleaned_data.get("_image") # only resize if an image is present and has changed if image and not isinstance(image, FieldFile): from NEMO.utilities import resize_image # resize image to 500x500 maximum cleaned_data["_image"] = resize_image(image, 500) if parent_tool: if parent_tool.id == self.instance.id: self.add_error("parent_tool", "You cannot select the parent to be the tool itself.") # in case of alternate tool, remove everything except parent_tool and name data = dict([(k, v) for k, v in self.cleaned_data.items() if k == "parent_tool" or k == "name"]) # an alternate tool is never visible data["visible"] = False return data else: if not category: self.add_error("_category", "This field is required.") if not location: self.add_error("_location", "This field is required.") if not phone_number: self.add_error("_phone_number", "This field is required.") if not primary_owner: self.add_error("_primary_owner", "This field is required.") post_usage_questions = cleaned_data.get("_post_usage_questions") # Validate _post_usage_questions JSON format if post_usage_questions: try: loads(post_usage_questions) except ValueError: self.add_error("_post_usage_questions", "This field needs to be a valid JSON string") try: DynamicForm(post_usage_questions, self.instance.id).validate() except Exception: error_info = sys.exc_info() self.add_error("_post_usage_questions", error_info[0].__name__ + ": " + str(error_info[1])) policy_off_between_times = cleaned_data.get("_policy_off_between_times") policy_off_start_time = cleaned_data.get("_policy_off_start_time") policy_off_end_time = cleaned_data.get("_policy_off_end_time") if policy_off_between_times and (not policy_off_start_time or not policy_off_end_time): if not policy_off_start_time: self.add_error("_policy_off_start_time", "Start time must be specified") if not policy_off_end_time: self.add_error("_policy_off_end_time", "End time must be specified")
def do_disable_tool(request, tool_id): tool = Tool.objects.get(id=tool_id) customer = User.objects.get(id=request.POST["customer_id"]) downtime = timedelta(minutes=quiet_int(request.POST.get("downtime"))) bypass_interlock = request.POST.get("bypass", "False") == "True" response = check_policy_to_disable_tool(tool, customer, downtime) if response.status_code != HTTPStatus.OK: dictionary = {"message": response.content, "delay": 10} return render(request, "kiosk/acknowledgement.html", dictionary) # All policy checks passed so try to disable the tool for the user. if tool.interlock and not tool.interlock.lock(): if bypass_interlock and interlock_bypass_allowed(customer): pass else: return interlock_error("Disable", customer) # Shorten the user's tool reservation since we are now done using the tool shorten_reservation(user=customer, item=tool, new_end=timezone.now() + downtime) # End the current usage event for the tool and save it. current_usage_event = tool.get_current_usage_event() current_usage_event.end = timezone.now() + downtime # Collect post-usage questions dynamic_form = DynamicForm(tool.post_usage_questions) try: current_usage_event.run_data = dynamic_form.extract(request) except RequiredUnansweredQuestionsException as e: if customer.is_staff and customer != current_usage_event.operator and current_usage_event.user != customer: # if a staff is forcing somebody off the tool and there are required questions, send an email and proceed current_usage_event.run_data = e.run_data email_managers_required_questions_disable_tool( current_usage_event.operator, customer, tool, e.questions) else: dictionary = {"message": str(e), "delay": 10} return render(request, "kiosk/acknowledgement.html", dictionary) dynamic_form.charge_for_consumables( current_usage_event.user, current_usage_event.operator, current_usage_event.project, current_usage_event.run_data, request, ) dynamic_form.update_tool_counters(current_usage_event.run_data, tool.id) current_usage_event.save() dictionary = { "message": "You are no longer using the {}".format(tool), "badge_number": customer.badge_number } return render(request, "kiosk/acknowledgement.html", dictionary)
def disable_tool(request): tool = Tool.objects.get(id=request.POST['tool_id']) customer = User.objects.get(id=request.POST['customer_id']) downtime = timedelta(minutes=quiet_int(request.POST.get('downtime'))) response = check_policy_to_disable_tool(tool, customer, downtime) if response.status_code != HTTPStatus.OK: dictionary = { 'message': response.content, 'delay': 10, } return render(request, 'kiosk/acknowledgement.html', dictionary) # Shorten the user's tool reservation since we are now done using the tool shorten_reservation(user=customer, item=tool, new_end=timezone.now() + downtime) # All policy checks passed so disable the tool for the user. if tool.interlock and not tool.interlock.lock(): raise Exception( "The interlock command for this tool failed. The error message returned: " + str(tool.interlock.most_recent_reply)) # End the current usage event for the tool and save it. current_usage_event = tool.get_current_usage_event() current_usage_event.end = timezone.now() + downtime # Collect post-usage questions dynamic_form = DynamicForm(tool.post_usage_questions) current_usage_event.run_data = dynamic_form.extract(request) dynamic_form.charge_for_consumables(current_usage_event.user, current_usage_event.operator, current_usage_event.project, current_usage_event.run_data) current_usage_event.save() dictionary = { 'message': 'You are no longer using the {}'.format(tool), 'badge_number': customer.badge_number, } return render(request, 'kiosk/acknowledgement.html', dictionary)
def questions_preview(self, obj): form_validity_div = "" rendered_form = "" try: rendered_form = DynamicForm(obj.questions).render( "reservation_group_question", obj.id) if obj.questions: form_validity_div = '<div id="form_validity"></div>' except: pass return mark_safe( '<div class="questions_preview">{}{}</div><div class="help questions_preview_help">Save form to preview reservation questions</div>' .format(rendered_form, form_validity_div))
def tool_status(request, tool_id): """ Gets the current status of the tool (that is, whether it is currently in use or not). """ tool = get_object_or_404(Tool, id=tool_id, visible=True) exclude=get_customization('exclude_from_usage') projects_to_exclude = [] if exclude: projects_to_exclude = [int(s) for s in exclude.split() if s.isdigit()] dictionary = { 'tool': tool, 'task_categories': TaskCategory.objects.filter(stage=TaskCategory.Stage.INITIAL_ASSESSMENT), 'rendered_configuration_html': tool.configuration_widget(request.user), 'mobile': request.device == 'mobile', 'task_statuses': TaskStatus.objects.exclude(name="default"), 'post_usage_questions': DynamicForm(tool.post_usage_questions).render(), 'active_projects_filtered': request.user.active_projects().exclude(id__in=projects_to_exclude) } try: current_reservation = Reservation.objects.get(start__lt=timezone.now(), end__gt=timezone.now(), cancelled=False, missed=False, shortened=False, user=request.user, tool=tool) if request.user == current_reservation.user: dictionary['time_left'] = current_reservation.end except Reservation.DoesNotExist: pass try: next_4hrs = Reservation.objects.filter(start__gt=timezone.now(), start__lte=timezone.now()+timedelta(hours=4), cancelled=False, missed=False, shortened=False, tool=tool).order_by('start') if next_4hrs: next_res = next_4hrs[0] dictionary['next_res'] = next_res except Reservation.DoesNotExist: pass # Staff need the user list to be able to qualify users for the tool. if request.user.is_staff: dictionary['users'] = User.objects.filter(is_active=True) return render(request, 'tool_control/tool_status.html', dictionary)