Пример #1
0
class EmailBroadcastForm(Form):
    subject = CharField(required=False)
    color = ChoiceField(choices=((bootstrap_primary_color('info'),
                                  'info'), (bootstrap_primary_color('success'),
                                            'success'),
                                 (bootstrap_primary_color('warning'),
                                  'warning'),
                                 (bootstrap_primary_color('danger'),
                                  'danger')))
    title = CharField(required=False)
    greeting = CharField(required=False)
    contents = CharField(required=False)
    copy_me = BooleanField(initial=True)

    audience = ChoiceField(choices=[('tool', 'tool'), ('project', 'project'),
                                    ('account', 'account'),
                                    ('tool_date', 'tool_date'),
                                    ('project_date', 'project_date'),
                                    ('user', 'user'), ('group', 'group')],
                           label="Audience")
    selection = IntegerField()
    recipient = CharField(required=False)
    only_active_users = BooleanField(initial=True)

    def clean_title(self):
        return self.cleaned_data['title'].upper()
Пример #2
0
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)
Пример #3
0
class EmailBroadcastForm(Form):
	subject = CharField(required=False)
	color = ChoiceField(
		choices=(
			(bootstrap_primary_color("info"), "info"),
			(bootstrap_primary_color("success"), "success"),
			(bootstrap_primary_color("warning"), "warning"),
			(bootstrap_primary_color("danger"), "danger"),
		)
	)
	title = CharField(required=False)
	greeting = CharField(required=False)
	contents = CharField(required=False)
	copy_me = BooleanField(initial=True)

	audience = ChoiceField(choices=[("tool", "tool"), ("project", "project"), ("account", "account"), ("area", "area"), ("user", "user")])
	selection = CharField(required=False)
	no_type = BooleanField(initial=False, required=False)
	only_active_users = BooleanField(initial=True)

	def clean_title(self):
		return self.cleaned_data["title"].upper()

	def clean_selection(self):
		return self.data.getlist('selection')
Пример #4
0
def email_reservation_reminders(request):
    # Exit early if the reservation reminder email template has not been customized for the organization yet.
    reservation_reminder_message = get_media_file_contents(
        'reservation_reminder_email.html')
    reservation_warning_message = get_media_file_contents(
        'reservation_warning_email.html')
    if not reservation_reminder_message or not reservation_warning_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 that are two hours from now, plus or minus 5 minutes to allow for time skew.
    preparation_time = 120
    tolerance = 5
    earliest_start = timezone.now() + timedelta(
        minutes=preparation_time) - timedelta(minutes=tolerance)
    latest_start = timezone.now() + timedelta(
        minutes=preparation_time) + timedelta(minutes=tolerance)
    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.
    for reservation in upcoming_reservations:
        tool = reservation.tool
        if tool.operational and not tool.problematic(
        ) and tool.all_resources_available():
            subject = reservation.tool.name + " reservation reminder"
            rendered_message = Template(reservation_reminder_message).render(
                Context({
                    'reservation': reservation,
                    'template_color': bootstrap_primary_color('success')
                }))
        elif not tool.operational or tool.required_resource_is_unavailable():
            subject = reservation.tool.name + " reservation problem"
            rendered_message = Template(reservation_warning_message).render(
                Context({
                    'reservation': reservation,
                    'template_color': bootstrap_primary_color('danger'),
                    'fatal_error': True
                }))
        else:
            subject = reservation.tool.name + " reservation warning"
            rendered_message = Template(reservation_warning_message).render(
                Context({
                    'reservation': reservation,
                    'template_color': bootstrap_primary_color('warning'),
                    'fatal_error': False
                }))
        user_office_email = get_customization('user_office_email_address')
        reservation.user.email_user(subject, rendered_message,
                                    user_office_email)
    return HttpResponse()
Пример #5
0
def send_alert_emails(alert):
    user_office_email = get_customization('user_office_email_address')
    facility_name = get_customization('facility_name')
    if facility_name == '':
        facility_name = "Facility"
    generic_email = get_media_file_contents('generic_email.html')
    if user_office_email and generic_email:
        users = User.objects.filter(is_active=True).exclude(is_staff=True)
        subject = alert.title
        title = f"{facility_name} Alert"
        color = bootstrap_primary_color('danger')
        greeting = 'Labmembers,'
        message = alert.contents
        dictionary = {
            'title': title,
            'greeting': greeting,
            'contents': message,
            'template_color': color,
        }
        msg = Template(generic_email).render(Context(dictionary))
        users = [x.email for x in users]
        email = EmailMultiAlternatives(subject,
                                       from_email=user_office_email,
                                       to=[user_office_email],
                                       bcc=set(users))
        email.attach_alternative(msg, 'text/html')
        email.send()
Пример #6
0
def cancel_the_reservation(reservation: Reservation, user_cancelling_reservation: User, reason: Optional[str]):
	response = check_policy_to_cancel_reservation(reservation, user_cancelling_reservation)
	# Staff must provide a reason when cancelling a reservation they do not own.
	if reservation.user != user_cancelling_reservation and not reason:
		response = HttpResponseBadRequest("You must provide a reason when cancelling someone else's reservation.")

	if response.status_code == HTTPStatus.OK:
		# All policy checks passed, so cancel the reservation.
		reservation.cancelled = True
		reservation.cancellation_time = timezone.now()
		reservation.cancelled_by = user_cancelling_reservation

		if reason:
			''' don't notify in this case since we are sending a specific email for the cancellation '''
			reservation.save()
			dictionary = {
				'staff_member': user_cancelling_reservation,
				'reservation': reservation,
				'reason': reason,
				'template_color': bootstrap_primary_color('info')
			}
			email_contents = get_media_file_contents('cancellation_email.html')
			if email_contents:
				cancellation_email = Template(email_contents).render(Context(dictionary))
				if getattr(reservation.user.preferences, 'attach_cancelled_reservation', False):
					attachment = create_ics_for_reservation(reservation, cancelled=True)
					reservation.user.email_user('Your reservation was cancelled', cancellation_email, user_cancelling_reservation.email, [attachment])
				else:
					reservation.user.email_user('Your reservation was cancelled', cancellation_email, user_cancelling_reservation.email)

		else:
			''' here the user cancelled his own reservation so notify him '''
			reservation.save_and_notify()

	return response
Пример #7
0
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)
Пример #8
0
class EmailBroadcastForm(Form):
    subject = CharField(required=False)
    color = ChoiceField(choices=(
        (bootstrap_primary_color("info"), "info"),
        (bootstrap_primary_color("success"), "success"),
        (bootstrap_primary_color("warning"), "warning"),
        (bootstrap_primary_color("danger"), "danger"),
    ))
    title = CharField(required=False)
    greeting = CharField(required=False)
    contents = CharField(required=False)
    copy_me = BooleanField(initial=True)

    audience = ChoiceField(
        choices=[('tool', 'tool'), ('project',
                                    'project'), ('account',
                                                 'account'), ('all', 'all')])
    selection = IntegerField()
    only_active_users = BooleanField(initial=True)

    def clean_title(self):
        return self.cleaned_data["title"].upper()
Пример #9
0
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 NanoFab 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, '', user.email, recipients, html_message=message)
Пример #10
0
def cancel_reservation(request, reservation_id):
    """ Cancel a reservation for a user. """
    reservation = get_object_or_404(Reservation, id=reservation_id)
    response = check_policy_to_cancel_reservation(reservation, request.user)
    # Staff must provide a reason when cancelling a reservation they do not own.
    reason = parse_parameter_string(request.POST, 'reason')
    if reservation.user != request.user and not reason:
        response = HttpResponseBadRequest(
            "You must provide a reason when cancelling someone else's reservation."
        )

    if response.status_code == HTTPStatus.OK:
        # All policy checks passed, so cancel the reservation.
        reservation.cancelled = True
        reservation.cancellation_time = timezone.now()
        reservation.cancelled_by = request.user
        reservation.save()

        if reason:
            dictionary = {
                'staff_member': request.user,
                'reservation': reservation,
                'reason': reason,
                'template_color': bootstrap_primary_color('info')
            }
            email_contents = get_media_file_contents('cancellation_email.html')
            if email_contents:
                cancellation_email = Template(email_contents).render(
                    Context(dictionary))
                reservation.user.email_user('Your reservation was cancelled',
                                            cancellation_email,
                                            request.user.email)

    if request.device == 'desktop':
        return response
    if request.device == 'mobile':
        if response.status_code == HTTPStatus.OK:
            return render(request, 'mobile/cancellation_result.html', {
                'event_type': 'Reservation',
                'tool': reservation.tool
            })
        else:
            return render(request, 'mobile/error.html',
                          {'message': response.content})
Пример #11
0
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()
Пример #12
0
def send_new_task_emails(request, task):
    message = get_media_file_contents('new_task_email.html')
    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 NanoFab 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))
        recipients = tuple([
            r for r in [
                task.tool.primary_owner.email, task.tool.secondary_owner.email,
                task.tool.notification_email_address
            ] if r
        ])
        send_mail(subject,
                  '',
                  request.user.email,
                  recipients,
                  html_message=message)

    # 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)
Пример #13
0
def send_new_task_emails(request, task):
    message = get_media_file_contents('new_task_email.html')
    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 NanoFab 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))
        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
            ] if r
        ])
        send_mail(subject,
                  '',
                  request.user.email,
                  recipients,
                  html_message=message)

    # 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)
        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()
        except SMTPException as e:
            dictionary = {
                'title':
                'Email not sent',
                'heading':
                'There was a problem sending your email',
                'content':
                'NEMO was unable to send the email through the email server. The error message that NEMO received is: '
                + str(e),
            }
Пример #14
0
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()
Пример #15
0
def send_task_update_emails(request, task):
    message = get_media_file_contents('generic_email.html')
    user_office_email = get_customization('user_office_email_address')
    if message and user_office_email:
        task.refresh_from_db()
        if not task.tool.problematic():
            tool_status = 'up'
        elif task.tool.operational:
            tool_status = 'in problem status'
        else:
            tool_status = 'down'
        if task.resolved:
            subject = f'{task.tool} Issue Resolved'
            task_user = task.resolver
        elif task.cancelled:
            subject = f'{task.tool} Issue Cancelled'
            task_user = task.resolver
        else:
            subject = f'{task.tool} Update'
            task_user = task.last_updated_by
        messagebody = f"""
	The status of the {task.tool} was just modified by {task_user}.
	You can see the full status description here: {request.build_absolute_uri(task.tool.get_absolute_url())}.

	{task.tool} is currently {tool_status}.
	"""
    if task.resolved:
        messagebody += f"""
	Resolution description:
	{task.resolution_description}
	"""
    dictionary = {
        'template_color':
        bootstrap_primary_color('success')
        if tool_status == 'up' else bootstrap_primary_color('warning'),
        'title':
        subject,
        'greeting':
        f'{task.tool} Users,',
        'contents':
        messagebody
    }
    users = User.objects.filter(qualifications__id=task.tool.id,
                                is_active=True)
    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()
    except SMTPException as e:
        dictionary = {
            'title':
            'Email not sent',
            'heading':
            'There was a problem sending your email',
            'content':
            'NEMO was unable to send the email through the email server. The error message that NEMO received is: '
            + str(e),
        }