def send_reorder_supply_reminder_email(consumable: Consumable): user_office_email = get_customization('user_office_email_address') message = get_media_file_contents('reorder_supplies_reminder_email.html') if user_office_email and message: subject = f"Time to order more {consumable.name}" rendered_message = Template(message).render(Context({'item': consumable})) send_mail(subject=subject, content=rendered_message, from_email=user_office_email, to=[consumable.reminder_email], email_category=EmailCategory.SYSTEM)
def feedback(request): recipient = get_customization('feedback_email_address') email_contents = get_media_file_contents('feedback_email.html') if not recipient or not email_contents: return render(request, 'feedback.html', {'customization_required': True}) if request.method == 'GET': return render(request, 'feedback.html') contents = parse_parameter_string(request.POST, 'feedback', FEEDBACK_MAXIMUM_LENGTH) if contents == '': return render(request, 'feedback.html') dictionary = { 'contents': contents, 'user': request.user, } email = Template(email_contents).render(Context(dictionary)) send_mail('Feedback from ' + str(request.user), email, request.user.email, [recipient]) dictionary = { 'title': 'Feedback', 'heading': 'Thanks for your feedback!', 'content': 'Your feedback has been sent to the NanoFab staff. We will follow up with you as soon as we can.', } return render(request, 'acknowledgement.html', dictionary)
def send_missed_reservation_notification(reservation): subject = "Missed reservation for the " + str(reservation.tool) message = get_media_file_contents('missed_reservation_email.html') message = Template(message).render(Context({'reservation': reservation})) user_office_email = get_customization('user_office_email_address') abuse_email = get_customization('abuse_email_address') send_mail(subject, message, user_office_email, [reservation.user.email, abuse_email, user_office_email])
def send_new_task_emails(request, task: Task, task_images: List[TaskImages]): message = get_media_file_contents('new_task_email.html') attachments = None if task_images: attachments = [create_email_attachment(task_image.image, task_image.image.name) for task_image in task_images] if message: dictionary = { 'template_color': bootstrap_primary_color('danger') if task.force_shutdown else bootstrap_primary_color('warning'), 'user': request.user, 'task': task, 'tool': task.tool, 'tool_control_absolute_url': request.build_absolute_uri(task.tool.get_absolute_url()) } # Send an email to the appropriate staff that a new task has been created: subject = ('SAFETY HAZARD: ' if task.safety_hazard else '') + task.tool.name + (' shutdown' if task.force_shutdown else ' problem') message = Template(message).render(Context(dictionary)) managers = [] if hasattr(settings, 'LAB_MANAGERS'): managers = settings.LAB_MANAGERS recipients = tuple([r for r in [task.tool.primary_owner.email, *task.tool.backup_owners.all().values_list('email', flat=True), task.tool.notification_email_address, *managers] if r]) send_mail(subject, message, request.user.email, recipients, attachments) # Send an email to any user (excluding staff) with a future reservation on the tool: user_office_email = get_customization('user_office_email_address') message = get_media_file_contents('new_task_email.html') if user_office_email and message: upcoming_reservations = Reservation.objects.filter(start__gt=timezone.now(), cancelled=False, tool=task.tool, user__is_staff=False) for reservation in upcoming_reservations: if not task.tool.operational: subject = reservation.tool.name + " reservation problem" rendered_message = Template(message).render(Context({'reservation': reservation, 'template_color': bootstrap_primary_color('danger'), 'fatal_error': True})) else: subject = reservation.tool.name + " reservation warning" rendered_message = Template(message).render(Context({'reservation': reservation, 'template_color': bootstrap_primary_color('warning'), 'fatal_error': False})) reservation.user.email_user(subject, rendered_message, user_office_email)
def send_task_updated_email(task, url, task_images: List[TaskImages]): if not hasattr(settings, 'LAB_MANAGERS'): return attachments = None if task_images: attachments = [ create_email_attachment(task_image.image, task_image.image.name) for task_image in task_images ] task.refresh_from_db() if task.resolved: task_user = task.resolver task_status = 'resolved' else: task_user = task.last_updated_by task_status = 'updated' message = f""" A task for the {task.tool} was just modified by {task_user}. <br/><br/> The latest update is at the bottom of the description. The entirety of the task status follows: <br/><br/> Task problem description:<br/> {task.problem_description} <br/><br/> Task progress description:<br/> {task.progress_description} <br/><br/> Task resolution description:<br/> {task.resolution_description} <br/><br/> Visit {url} to view the tool control page for the task.<br/> """ send_mail(f'{task.tool} task {task_status}', message, settings.SERVER_EMAIL, settings.LAB_MANAGERS, attachments)
def reset_tool_counter(request, counter_id): counter = get_object_or_404(ToolUsageCounter, id=counter_id) counter.last_reset_value = counter.value counter.value = 0 counter.last_reset = datetime.now() counter.last_reset_by = request.user counter.save() # Save a comment about the counter being reset. comment = Comment() comment.tool = counter.tool comment.content = f"The {counter.name} counter was reset to 0. Its last value was {counter.last_reset_value}." comment.author = request.user comment.expiration_date = datetime.now() + timedelta(weeks=1) comment.save() # Send an email to Lab Managers about the counter being reset. if hasattr(settings, "LAB_MANAGERS"): message = f"""The {counter.name} counter for the {counter.tool.name} was reset to 0 on {formats.localize(counter.last_reset)} by {counter.last_reset_by}. Its last value was {counter.last_reset_value}.""" send_mail( subject=f"{counter.tool.name} counter reset", content=message, from_email=settings.SERVER_EMAIL, to=settings.LAB_MANAGERS, email_category=EmailCategory.SYSTEM, ) return redirect("tool_control")
def send_email(request): try: recipient = request.POST['recipient'] validate_email(recipient) recipient_list = [recipient] except: return HttpResponseBadRequest('The intended recipient was not a valid email address. The email was not sent.') sender = request.user.email subject = request.POST.get('subject') body = request.POST.get('body') if request.POST.get('copy_me'): recipient_list.append(sender) try: send_mail(subject=subject, content=body, from_email=sender, bcc=recipient_list, email_category=EmailCategory.DIRECT_CONTACT) except SMTPException as error: site_title = get_customization('site_title') error_message = f'{site_title} was unable to send the email through the email server. The error message that was received is: ' + str(error) logger.exception(error_message) dictionary = { 'title': 'Email not sent', 'heading': 'There was a problem sending your email', 'content': error_message, } return render(request, 'acknowledgement.html', dictionary) dictionary = { 'title': 'Email sent', 'heading': 'Your email was sent', } return render(request, 'acknowledgement.html', dictionary)
def facility_rules(request): if request.method == "GET" and (request.user.training_required or request.user.access_expiration < timezone.now().date() + timedelta(weeks=2) or request.user.is_staff): tutorial = get_media_file_contents("facility_rules_tutorial.html") if tutorial: dictionary = { "active_user_count": User.objects.filter(is_active=True).count(), "active_project_count": Project.objects.filter(active=True).count(), } tutorial = Template(tutorial).render( RequestContext(request, dictionary)) return render(request, "facility_rules.html", {"facility_rules_tutorial": tutorial}) elif request.method == "POST": facility_name = get_customization("facility_name") summary = request.POST.get("making_reservations_summary", "").strip()[:3000] dictionary = { "user": request.user, "making_reservations_rule_summary": summary } abuse_email = get_customization("abuse_email_address") email_contents = get_media_file_contents( "facility_rules_tutorial_email.html") if abuse_email and email_contents: message = Template(email_contents, dictionary).render(Context(dictionary)) send_mail( subject=f"{facility_name} rules tutorial", content=message, from_email=abuse_email, to=[abuse_email], email_category=EmailCategory.SYSTEM, ) dictionary = { "title": f"{facility_name} rules tutorial", "heading": "Tutorial complete!", "content": "Tool usage and reservation privileges have been enabled on your user account.", } request.user.training_required = False while request.user.access_expiration < timezone.now().date( ) + timedelta(weeks=2): try: request.user.access_expiration = request.user.access_expiration.replace( year=request.user.access_expiration.year + 1) except ValueError: request.user.access_expiration = request.user.access_expiration.replace( year=request.user.access_expiration.year + 1, day=request.user.access_expiration.day - 1) request.user.save() return render(request, "acknowledgement.html", dictionary)
def send_broadcast_email(request): content = get_media_file_contents('generic_email.html') if not content: return HttpResponseBadRequest('Generic email template not defined. Visit the customization page to upload a template.') form = EmailBroadcastForm(request.POST) if not form.is_valid(): return render(request, 'email/compose_email.html', {'form': form}) dictionary = { 'title': form.cleaned_data['title'], 'greeting': form.cleaned_data['greeting'], 'contents': form.cleaned_data['contents'], 'template_color': form.cleaned_data['color'], } content = Template(content).render(Context(dictionary)) users = None audience = form.cleaned_data['audience'] selection = form.cleaned_data['selection'] active_choice = form.cleaned_data['only_active_users'] try: if audience == 'tool': users = User.objects.filter(qualifications__id=selection) elif audience == 'project': users = User.objects.filter(projects__id=selection) elif audience == 'account': users = User.objects.filter(projects__account__id=selection) if active_choice: users = users.filter(is_active=True) except Exception as error: warning_message = 'Your email was not sent. There was a problem finding the users to send the email to.' dictionary = {'error': warning_message} logger.warning(warning_message + ' audience: {}, only_active: {}. The error message that was received is: {}'.format(audience, active_choice, str(error))) return render(request, 'email/compose_email.html', dictionary) if not users: dictionary = {'error': 'The audience you specified is empty. You must send the email to at least one person.'} return render(request, 'email/compose_email.html', dictionary) subject = form.cleaned_data['subject'] users = [x.email for x in users] if form.cleaned_data['copy_me']: users += [request.user.email] try: send_mail(subject=subject, content=content, from_email=request.user.email, bcc=set(users), email_category=EmailCategory.BROADCAST_EMAIL) except SMTPException as error: site_title = get_customization('site_title') error_message = f"{site_title} was unable to send the email through the email server. The error message that was received is: " + str(error) logger.exception(error_message) dictionary = { 'title': 'Email not sent', 'heading': 'There was a problem sending your email', 'content': error_message, } return render(request, 'acknowledgement.html', dictionary) dictionary = { 'title': 'Email sent', 'heading': 'Your email was sent', } return render(request, 'acknowledgement.html', dictionary)
def send_missed_reservation_notification(reservation): subject = "Missed reservation for the " + str(reservation.reservation_item) message = get_media_file_contents('missed_reservation_email.html') user_office_email = get_customization('user_office_email_address') abuse_email = get_customization('abuse_email_address') if message and user_office_email: message = Template(message).render(Context({'reservation': reservation})) send_mail(subject, message, user_office_email, [reservation.user.email, abuse_email, user_office_email]) else: calendar_logger.error("Missed reservation email couldn't be send because missed_reservation_email.html or user_office_email are not defined")
def send_out_of_time_reservation_notification(reservation:Reservation): subject = "Out of time in the " + str(reservation.area.name) message = get_media_file_contents('out_of_time_reservation_email.html') user_office_email = get_customization('user_office_email_address') if message and user_office_email: message = Template(message).render(Context({'reservation': reservation})) recipients = [reservation.user.email] recipients.extend(reservation.area.abuse_email_list()) send_mail(subject, message, user_office_email, recipients) else: calendar_logger.error("Out of time reservation email couldn't be send because out_of_time_reservation_email.html or user_office_email are not defined")
def send_safety_email_notification(request, issue): recipient = get_customization('safety_email_address') message = get_media_file_contents('safety_issue_email.html') if recipient and message: subject = 'Safety issue' dictionary = { 'issue': issue, 'issue_absolute_url': request.build_absolute_uri(issue.get_absolute_url()), } rendered_message = Template(message).render(Context(dictionary)) from_email = issue.reporter.email if issue.reporter else recipient send_mail(subject=subject, content=rendered_message, from_email=from_email, to=[recipient], email_category=EmailCategory.SAFETY)
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) # 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 = "NanoFab usage" for user in aggregate.values(): rendered_message = Template(message).render(Context({'user': user})) send_mail(subject, rendered_message, user_office_email, [user['email']]) 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 set_task_status(request, task, status_name, user): if not user.is_staff and status_name: raise ValueError("Only staff can set task status") #If no status is given, assign to default status. This will make sure all tasks have a proper Task History if not status_name: status_name = "default" status = TaskStatus.objects.get_or_create(name=status_name) TaskHistory.objects.create(task=task, status=status_name, user=user, shutdown=task.force_shutdown) status_message = f'On {format_datetime(timezone.now())}, {user.get_full_name()} set the status of this task to "{status_name}".' task.progress_description = status_message if task.progress_description is None else task.progress_description + '\n\n' + status_message task.save() message = get_media_file_contents('task_status_notification.html') # Send an email to the appropriate staff that a task status has been updated: if message: dictionary = { 'template_color': bootstrap_primary_color('success'), 'title': f'{task.tool} task notification', 'status_message': status_message, 'notification_message': status.notification_message, 'task': task, 'tool_control_absolute_url': request.build_absolute_uri(task.tool.get_absolute_url()) } subject = f'{task.tool} task notification' message = Template(message).render(Context(dictionary)) recipients = [ task.tool.primary_tool_owner.email if status.notify_primary_tool_owner else None, task.tool.notification_email_address if status.notify_tool_notification_email else None, status.custom_notification_email_address ] if status.notify_backup_tool_owners: recipients += task.tool.backup_tool_owners.values_list('email') recipients = filter(None, recipients) send_mail(subject=subject, content=message, from_email=user.email, to=recipients, email_category=EmailCategory.TASKS)
def send_tool_usage_counter_email(counter: ToolUsageCounter): user_office_email = get_customization('user_office_email_address') message = get_media_file_contents('counter_threshold_reached_email.html') if user_office_email and message: subject = f"Warning threshold reached for {counter.tool.name} {counter.name} counter" rendered_message = Template(message).render( Context({'counter': counter})) send_mail(subject=subject, content=rendered_message, from_email=user_office_email, to=counter.warning_email, email_category=EmailCategory.SYSTEM)
def send_safety_email_notification(request, issue): subject = 'Safety issue' dictionary = { 'issue': issue, 'issue_absolute_url': request.build_absolute_uri(issue.get_absolute_url()), } recipient = get_customization('safety_email_address') message = get_media_file_contents('safety_issue_email.html') if not recipient or not message: return rendered_message = Template(message).render(Context(dictionary)) from_email = issue.reporter.email if issue.reporter else recipient send_mail(subject, rendered_message, from_email, [recipient])
def send_task_updated_email(task, url, task_images: List[TaskImages] = None): try: if not hasattr(settings, 'LAB_MANAGERS'): return attachments = None if task_images: attachments = [ create_email_attachment(task_image.image, task_image.image.name) for task_image in task_images ] task.refresh_from_db() if task.cancelled: task_user = task.resolver task_status = 'cancelled' elif task.resolved: task_user = task.resolver task_status = 'resolved' else: task_user = task.last_updated_by task_status = 'updated' message = f""" A task for the {task.tool} was just modified by {task_user}. <br/><br/> The latest update is at the bottom of the description. The entirety of the task status follows: <br/><br/> Task problem description:<br/> {linebreaksbr(task.problem_description)} <br/><br/> Task progress description:<br/> {linebreaksbr(task.progress_description)} <br/><br/> Task resolution description:<br/> {linebreaksbr(task.resolution_description)} <br/><br/> Visit {url} to view the tool control page for the task.<br/> """ send_mail(subject=f'{task.tool} task {task_status}', content=message, from_email=settings.SERVER_EMAIL, to=settings.LAB_MANAGERS, attachments=attachments, email_category=EmailCategory.TASKS) except Exception as error: site_title = get_customization('site_title') error_message = f"{site_title} was unable to send the task updated email. The error message that was received is: " + str( error) tasks_logger.exception(error_message)
def send_user_cancelled_reservation_notification(reservation: Reservation): site_title = get_customization('site_title') recipients = [reservation.user.email] if getattr(reservation.user.preferences, 'attach_cancelled_reservation', False) else [] if reservation.area: recipients.extend(reservation.area.reservation_email_list()) if recipients: subject = f"[{site_title}] Cancelled Reservation for the " + str(reservation.reservation_item) message = get_media_file_contents('reservation_cancelled_user_email.html') message = Template(message).render(Context({'reservation': reservation})) user_office_email = get_customization('user_office_email_address') # We don't need to check for existence of reservation_cancelled_user_email because we are attaching the ics reservation and sending the email regardless (message will be blank) if user_office_email: attachment = create_ics_for_reservation(reservation, cancelled=True) send_mail(subject, message, user_office_email, recipients, [attachment]) else: calendar_logger.error("User cancelled reservation notification could not be send because user_office_email_address is not defined")
def facility_rules(request): if request.method == 'GET': tutorial = get_media_file_contents('facility_rules_tutorial.html') if tutorial: dictionary = { 'active_user_count': User.objects.filter(is_active=True).count(), 'active_project_count': Project.objects.filter(active=True).count(), } tutorial = Template(tutorial).render( RequestContext(request, dictionary)) return render(request, 'facility_rules.html', {'facility_rules_tutorial': tutorial}) elif request.method == 'POST': facility_name = get_customization('facility_name') summary = request.POST.get('making_reservations_summary', '').strip()[:3000] dictionary = { 'user': request.user, 'making_reservations_rule_summary': summary, } abuse_email = get_customization('abuse_email_address') email_contents = get_media_file_contents( 'facility_rules_tutorial_email.html') if abuse_email and email_contents: message = Template(email_contents, dictionary).render(Context(dictionary)) send_mail(f"{facility_name} rules tutorial", message, abuse_email, [abuse_email]) dictionary = { 'title': f"{facility_name} rules tutorial", 'heading': 'Tutorial complete!', 'content': 'Tool usage and reservation privileges have been enabled on your user account.', } request.user.training_required = False request.user.save() return render(request, 'acknowledgement.html', dictionary)
def set_task_status(request, task, status_name, user): if not status_name: return if not user.is_staff: raise ValueError("Only staff can set task status") status = TaskStatus.objects.get(name=status_name) TaskHistory.objects.create(task=task, status=status_name, user=user) status_message = f'On {format_datetime(timezone.now())}, {user.get_full_name()} set the status of this task to "{status_name}".' task.progress_description = status_message if task.progress_description is None else task.progress_description + '\n\n' + status_message task.save() message = get_media_file_contents('task_status_notification.html') if not message: return dictionary = { 'template_color': bootstrap_primary_color('success'), 'title': f'{task.tool} task notification', 'status_message': status_message, 'notification_message': status.notification_message, 'task': task, 'tool_control_absolute_url': request.build_absolute_uri(task.tool.get_absolute_url()) } # Send an email to the appropriate staff that a new task has been created: subject = f'{task.tool} task notification' message = Template(message).render(Context(dictionary)) recipients = [ task.tool.primary_tool_owner.email if status.notify_primary_tool_owner else None, task.tool.notification_email_address if status.notify_tool_notification_email else None, status.custom_notification_email_address ] if status.notify_backup_tool_owners: recipients += task.tool.backup_tool_owners.values_list('email') recipients = filter(None, recipients) send_mail(subject, message, user.email, recipients)
def email_managers_required_questions_disable_tool( tool_user: User, staff_member: User, tool: Tool, questions: List[PostUsageQuestion]): abuse_email_address = get_customization('abuse_email_address') managers = [] if hasattr(settings, 'LAB_MANAGERS'): managers = settings.LAB_MANAGERS ccs = set( tuple([ r for r in [ staff_member.email, tool.primary_owner.email, *tool. backup_owners.all().values_list('email', flat=True), *managers ] if r ])) display_questions = "".join([ linebreaksbr(mark_safe(question.render_as_text())) + "<br/><br/>" for question in questions ]) message = f""" Dear {tool_user.get_name()},<br/> You have been logged off by staff from the {tool} that requires answers to the following post-usage questions:<br/> <br/> {display_questions} <br/> Regards,<br/> <br/> NanoFab Management<br/> """ send_mail( subject= f"Unanswered post‑usage questions after logoff from the {tool.name}", content=message, from_email=abuse_email_address, to=[tool_user.email], cc=ccs, email_category=EmailCategory.ABUSE)
def check_policy_to_enable_tool(tool: Tool, operator: User, user: User, project: Project, staff_charge: bool): """ Check that the user is allowed to enable the tool. Enable the tool if the policy checks pass. """ facility_name = get_customization('facility_name') # The tool must be visible (or the parent if it's a child tool) to users. visible = tool.parent_tool.visible if tool.is_child_tool( ) else tool.visible if not visible: return HttpResponseBadRequest( "This tool is currently hidden from users.") # The tool must be operational. # If the tool is non-operational then it may only be accessed by staff members. if not tool.operational and not operator.is_staff: return HttpResponseBadRequest( "This tool is currently non-operational.") # The tool must not be in use. current_usage_event = tool.get_current_usage_event() if current_usage_event: return HttpResponseBadRequest("The tool is currently being used by " + str(current_usage_event.user) + ".") # The user must be qualified to use the tool itself, or the parent tool in case of alternate tool. tool_to_check_qualifications = tool.parent_tool if tool.is_child_tool( ) else tool if tool_to_check_qualifications not in operator.qualifications.all( ) and not operator.is_staff: return HttpResponseBadRequest( "You are not qualified to use this tool.") # Only staff members can operate a tool on behalf of another user. if (user and operator.pk != user.pk) and not operator.is_staff: return HttpResponseBadRequest( "You must be a staff member to use a tool on another user's behalf." ) # All required resources must be available to operate a tool except for staff. if tool.required_resource_set.filter( available=False).exists() and not operator.is_staff: return HttpResponseBadRequest( "A resource that is required to operate this tool is unavailable.") # The tool operator may not activate tools in a particular area unless they are logged in to the area. # Staff are exempt from this rule. if tool.requires_area_access and AreaAccessRecord.objects.filter( area=tool.requires_area_access, customer=operator, staff_charge=None, end=None).count() == 0 and not operator.is_staff: dictionary = {'operator': operator, 'tool': tool, 'type': 'access'} abuse_email_address = get_customization('abuse_email_address') message = get_media_file_contents( 'unauthorized_tool_access_email.html') if abuse_email_address and message: rendered_message = Template(message).render(Context(dictionary)) send_mail("Area access requirement", rendered_message, abuse_email_address, [abuse_email_address]) return HttpResponseBadRequest( "You must be logged in to the {} to operate this tool.".format( tool.requires_area_access.name)) # The tool operator may not activate tools in a particular area unless they are still within that area reservation window if not operator.is_staff and tool.requires_area_reservation(): if not tool.requires_area_access.get_current_reservation_for_user( operator): dictionary = { 'operator': operator, 'tool': tool, 'type': 'reservation', } abuse_email_address = get_customization('abuse_email_address') message = get_media_file_contents( 'unauthorized_tool_access_email.html') if abuse_email_address and message: rendered_message = Template(message).render( Context(dictionary)) send_mail("Area reservation requirement", rendered_message, abuse_email_address, [abuse_email_address]) return HttpResponseBadRequest( "You must have a current reservation for the {} to operate this tool." .format(tool.requires_area_access.name)) # Staff may only charge staff time for one user at a time. if staff_charge and operator.charging_staff_time(): return HttpResponseBadRequest( 'You are already charging staff time. You must end the current staff charge before you being another.' ) # Staff may not bill staff time to the themselves. if staff_charge and operator == user: return HttpResponseBadRequest( 'You cannot charge staff time to yourself.') # Users may only charge to projects they are members of. if project not in user.active_projects(): return HttpResponseBadRequest( 'The designated user is not assigned to the selected project.') # The tool operator must not have a lock on usage if operator.training_required: return HttpResponseBadRequest( f"You are blocked from using all tools in the {facility_name}. Please complete the {facility_name} rules tutorial in order to use tools." ) # Users may only use a tool when delayed logoff is not in effect. Staff are exempt from this rule. if tool.delayed_logoff_in_progress() and not operator.is_staff: return HttpResponseBadRequest( "Delayed tool logoff is in effect. You must wait for the delayed logoff to expire before you can use the tool." ) # Users may not enable a tool during a scheduled outage. Staff are exempt from this rule. if tool.scheduled_outage_in_progress() and not operator.is_staff: return HttpResponseBadRequest( "A scheduled outage is in effect. You must wait for the outage to end before you can use the tool." ) return HttpResponse()
def check_policy_to_enable_tool(tool: Tool, operator: User, user: User, project: Project, staff_charge: bool): """ Check that the user is allowed to enable the tool. Enable the tool if the policy checks pass. """ facility_name = get_customization('facility_name') # The tool must be visible (or the parent if it's a child tool) to users. visible = tool.parent_tool.visible if tool.is_child_tool() else tool.visible if not visible: return HttpResponseBadRequest("This tool is currently hidden from users.") # The tool must be operational. # If the tool is non-operational then it may only be accessed by staff members or service personnel. if not tool.operational and not operator.is_staff and not operator.is_service_personnel: return HttpResponseBadRequest("This tool is currently non-operational.") # The tool must not be in use. current_usage_event = tool.get_current_usage_event() if current_usage_event: return HttpResponseBadRequest("The tool is currently being used by " + str(current_usage_event.user) + ".") # The user must be qualified to use the tool itself, or the parent tool in case of alternate tool. tool_to_check_qualifications = tool.parent_tool if tool.is_child_tool() else tool if tool_to_check_qualifications not in operator.qualifications.all() and not operator.is_staff: return HttpResponseBadRequest("You are not qualified to use this tool.") # Only staff members can operate a tool on behalf of another user. if (user and operator.pk != user.pk) and not operator.is_staff: return HttpResponseBadRequest("You must be a staff member to use a tool on another user's behalf.") # All required resources must be available to operate a tool except for staff or service personnel. if tool.required_resource_set.filter(available=False).exists() and not operator.is_staff and not operator.is_service_personnel: return HttpResponseBadRequest("A resource that is required to operate this tool is unavailable.") # The tool operator may not activate tools in a particular area unless they are logged in to the area. # Staff are exempt from this rule. if tool.requires_area_access and AreaAccessRecord.objects.filter(area=tool.requires_area_access, customer=operator, staff_charge=None, end=None).count() == 0 and not operator.is_staff: abuse_email_address = get_customization('abuse_email_address') message = get_media_file_contents('unauthorized_tool_access_email.html') if abuse_email_address and message: dictionary = { 'operator': operator, 'tool': tool, 'type': 'access' } rendered_message = Template(message).render(Context(dictionary)) send_mail(subject="Area access requirement", content=rendered_message, from_email=abuse_email_address, to=[abuse_email_address], email_category=EmailCategory.ABUSE) return HttpResponseBadRequest("You must be logged in to the {} to operate this tool.".format(tool.requires_area_access.name)) # The tool operator may not activate tools in a particular area unless they are still within that area reservation window # Staff and service personnel are exempt from this rule. if not operator.is_staff and not operator.is_service_personnel and tool.requires_area_reservation(): if not tool.requires_area_access.get_current_reservation_for_user(operator): abuse_email_address = get_customization('abuse_email_address') message = get_media_file_contents('unauthorized_tool_access_email.html') if abuse_email_address and message: dictionary = { 'operator': operator, 'tool': tool, 'type': 'reservation', } rendered_message = Template(message).render(Context(dictionary)) send_mail(subject="Area reservation requirement", content=rendered_message, from_email=abuse_email_address, to=[abuse_email_address], email_category=EmailCategory.ABUSE) return HttpResponseBadRequest("You must have a current reservation for the {} to operate this tool.".format(tool.requires_area_access.name)) # Staff may only charge staff time for one user at a time. if staff_charge and operator.charging_staff_time(): return HttpResponseBadRequest('You are already charging staff time. You must end the current staff charge before you being another.') # Staff may not bill staff time to themselves. if staff_charge and operator == user: return HttpResponseBadRequest('You cannot charge staff time to yourself.') # Users may only charge to projects they are members of. if project not in user.active_projects(): return HttpResponseBadRequest('The designated user is not assigned to the selected project.') # The tool operator must not have a lock on usage if operator.training_required: return HttpResponseBadRequest(f"You are blocked from using all tools in the {facility_name}. Please complete the {facility_name} rules tutorial in order to use tools.") # Users may only use a tool when delayed logoff is not in effect. Staff and service personnel are exempt from this rule. if tool.delayed_logoff_in_progress() and not operator.is_staff and not operator.is_service_personnel: return HttpResponseBadRequest("Delayed tool logoff is in effect. You must wait for the delayed logoff to expire before you can use the tool.") # Users may not enable a tool during a scheduled outage. Staff and service personnel are exempt from this rule. if tool.scheduled_outage_in_progress() and not operator.is_staff and not operator.is_service_personnel: return HttpResponseBadRequest("A scheduled outage is in effect. You must wait for the outage to end before you can use the tool.") #Refuses all tool logins if user is logged in using an excluded project (i.e. one reserved for buddy system or observation) if not operator.is_staff: projects_to_exclude = [] exclude=get_customization('exclude_from_usage') if exclude: projects_to_exclude = [int(s) for s in exclude.split() if s.isdigit()] try: if tool.requires_area_access: current_access = AreaAccessRecord.objects.filter(area=tool.requires_area_access, customer=operator, staff_charge=None, end=None) if current_access[0].project.id in projects_to_exclude: return HttpResponseBadRequest("You may not use tools while logged in with this project.") except: return HttpResponseBadRequest("There was a problem enabling this tool. Please see staff.") if tool.reservation_required and not operator.is_staff: td=timedelta(minutes=15) if not Reservation.objects.filter(start__lt=timezone.now()+td, end__gt=timezone.now(), cancelled=False, missed=False, shortened=False, user=operator, tool=tool).exists(): return HttpResponseBadRequest("A reservation is required to enable this tool.") return HttpResponse()
def send_new_task_emails(request, task: Task, task_images: List[TaskImages]): message = get_media_file_contents('new_task_email.html') attachments = None if task_images: attachments = [ create_email_attachment(task_image.image, task_image.image.name) for task_image in task_images ] # Send an email to the appropriate staff that a new task has been created: if message: dictionary = { 'template_color': bootstrap_primary_color('danger') if task.force_shutdown else bootstrap_primary_color('warning'), 'user': request.user, 'task': task, 'tool': task.tool, 'tool_control_absolute_url': request.build_absolute_uri(task.tool.get_absolute_url()) } subject = ('SAFETY HAZARD: ' if task.safety_hazard else '') + task.tool.name + (' shutdown' if task.force_shutdown else ' problem') message = Template(message).render(Context(dictionary)) managers = [] if hasattr(settings, 'LAB_MANAGERS'): managers = settings.LAB_MANAGERS recipients = tuple([ r for r in [ task.tool.primary_owner.email, *task.tool.backup_owners.all().values_list('email', flat=True), task.tool.notification_email_address, *managers ] if r ]) send_mail(subject=subject, content=message, from_email=request.user.email, to=recipients, attachments=attachments, email_category=EmailCategory.TASKS) # Send an email to all users (excluding staff) qualified on the tool: user_office_email = get_customization('user_office_email_address') message = get_media_file_contents('new_task_email.html') if user_office_email and message: users = User.objects.filter(qualifications__id=task.tool.id, is_staff=False, is_active=True) dictionary = { 'template_color': bootstrap_primary_color('danger') if task.force_shutdown else bootstrap_primary_color('warning'), 'user': request.user, 'task': task, 'tool': task.tool, 'tool_control_absolute_url': request.build_absolute_uri(task.tool.get_absolute_url()), 'labmember': True } subject = task.tool.name + (' shutdown' if task.force_shutdown else ' problem') users = [x.email for x in users] rendered_message = Template(message).render(Context(dictionary)) #try: email = EmailMultiAlternatives(subject, from_email=user_office_email, bcc=set(users)) email.attach_alternative(rendered_message, 'text/html') email.send()