Move update ticket logic out of staff.py

This commit is contained in:
Timothy Hobbs 2023-11-14 21:17:37 +01:00
parent 6cd5522099
commit ade4c3115e
4 changed files with 411 additions and 354 deletions

385
helpdesk/update_ticket.py Normal file
View File

@ -0,0 +1,385 @@
import typing
from django.core.exceptions import ValidationError
from django.contrib.auth import get_user_model
from django.utils import timezone
from django.utils.translation import gettext as _
from helpdesk.lib import safe_template_context
from helpdesk import settings as helpdesk_settings
from helpdesk.lib import process_attachments
from helpdesk.decorators import (
is_helpdesk_staff,
)
from helpdesk.models import (
FollowUp,
Ticket,
TicketCC,
TicketChange,
)
User = get_user_model()
def add_staff_subscription(
user: User,
ticket: Ticket
) -> None:
"""Auto subscribe the staff member if that's what the settigs say and the
user is authenticated and a staff member"""
if helpdesk_settings.HELPDESK_AUTO_SUBSCRIBE_ON_TICKET_RESPONSE \
and user.is_authenticated \
and return_ticketccstring_and_show_subscribe(user, ticket)[1]:
subscribe_to_ticket_updates(ticket, user)
def return_ticketccstring_and_show_subscribe(user, ticket):
"""used in view_ticket() and followup_edit()"""
# create the ticketcc_string and check whether current user is already
# subscribed
username = user.get_username().upper()
try:
useremail = user.email.upper()
except AttributeError:
useremail = ""
strings_to_check = list()
strings_to_check.append(username)
strings_to_check.append(useremail)
ticketcc_string = ''
all_ticketcc = ticket.ticketcc_set.all()
counter_all_ticketcc = len(all_ticketcc) - 1
show_subscribe = True
for i, ticketcc in enumerate(all_ticketcc):
ticketcc_this_entry = str(ticketcc.display)
ticketcc_string += ticketcc_this_entry
if i < counter_all_ticketcc:
ticketcc_string += ', '
if strings_to_check.__contains__(ticketcc_this_entry.upper()):
show_subscribe = False
# check whether current user is a submitter or assigned to ticket
assignedto_username = str(ticket.assigned_to).upper()
strings_to_check = list()
if ticket.submitter_email is not None:
submitter_email = ticket.submitter_email.upper()
strings_to_check.append(submitter_email)
strings_to_check.append(assignedto_username)
if strings_to_check.__contains__(username) or strings_to_check.__contains__(useremail):
show_subscribe = False
return ticketcc_string, show_subscribe
def subscribe_to_ticket_updates(ticket, user=None, email=None, can_view=True, can_update=False):
if ticket is not None:
queryset = TicketCC.objects.filter(
ticket=ticket, user=user, email=email)
# Don't create duplicate entries for subscribers
if queryset.count() > 0:
return queryset.first()
if user is None and len(email) < 5:
raise ValidationError(
_('When you add somebody on Cc, you must provide either a User or a valid email. Email: %s' % email)
)
return ticket.ticketcc_set.create(
user=user,
email=email,
can_view=can_view,
can_update=can_update
)
def get_and_set_ticket_status(
new_status: int,
ticket: Ticket,
follow_up: FollowUp
) -> typing.Tuple[str, int]:
"""Performs comparision on previous status to new status,
updating the title as required.
Returns:
The old status as a display string, old status code string
"""
old_status_str = ticket.get_status_display()
old_status = ticket.status
if new_status != ticket.status:
ticket.status = new_status
ticket.save()
follow_up.new_status = new_status
if follow_up.title:
follow_up.title += ' and %s' % ticket.get_status_display()
else:
follow_up.title = '%s' % ticket.get_status_display()
if not follow_up.title:
if follow_up.comment:
follow_up.title = _('Comment')
else:
follow_up.title = _('Updated')
follow_up.save()
return old_status_str, old_status
def update_messages_sent_to_by_public_and_status(
public: bool,
ticket: Ticket,
follow_up: FollowUp,
context: str,
messages_sent_to: typing.Set[str],
files: typing.List[typing.Tuple[str, str]]
) -> Ticket:
"""Sets the status of the ticket"""
if public and (
follow_up.comment or (
follow_up.new_status in (
Ticket.RESOLVED_STATUS,
Ticket.CLOSED_STATUS
)
)
):
if follow_up.new_status == Ticket.RESOLVED_STATUS:
template = 'resolved_'
elif follow_up.new_status == Ticket.CLOSED_STATUS:
template = 'closed_'
else:
template = 'updated_'
roles = {
'submitter': (template + 'submitter', context),
'ticket_cc': (template + 'cc', context),
}
if ticket.assigned_to and ticket.assigned_to.usersettings_helpdesk.email_on_ticket_change:
roles['assigned_to'] = (template + 'cc', context)
messages_sent_to.update(
ticket.send(
roles,
dont_send_to=messages_sent_to,
fail_silently=True,
files=files
)
)
return ticket
def get_template_staff_and_template_cc(
reassigned, follow_up: FollowUp
) -> typing.Tuple[str, str]:
if reassigned:
template_staff = 'assigned_owner'
elif follow_up.new_status == Ticket.RESOLVED_STATUS:
template_staff = 'resolved_owner'
elif follow_up.new_status == Ticket.CLOSED_STATUS:
template_staff = 'closed_owner'
else:
template_staff = 'updated_owner'
if reassigned:
template_cc = 'assigned_cc'
elif follow_up.new_status == Ticket.RESOLVED_STATUS:
template_cc = 'resolved_cc'
elif follow_up.new_status == Ticket.CLOSED_STATUS:
template_cc = 'closed_cc'
else:
template_cc = 'updated_cc'
return template_staff, template_cc
def update_ticket(
user,
ticket,
title=None,
comment=None,
files=None,
public=False,
owner=-1,
priority=-1,
new_status=None,
time_spent=None,
due_date=None,
new_checklists=None,
):
# We need to allow the 'ticket' and 'queue' contexts to be applied to the
# comment.
context = safe_template_context(ticket)
if priority == -1:
priority = ticket.priority
if new_status is None:
new_status = ticket.status
if new_checklists is None:
new_checklists = {}
from django.template import engines
template_func = engines['django'].from_string
# this prevents system from trying to render any template tags
# broken into two stages to prevent changes from first replace being themselves
# changed by the second replace due to conflicting syntax
comment = comment.replace(
'{%', 'X-HELPDESK-COMMENT-VERBATIM').replace('%}', 'X-HELPDESK-COMMENT-ENDVERBATIM')
comment = comment.replace(
'X-HELPDESK-COMMENT-VERBATIM', '{% verbatim %}{%'
).replace(
'X-HELPDESK-COMMENT-ENDVERBATIM', '%}{% endverbatim %}'
)
# render the neutralized template
comment = template_func(comment).render(context)
if owner == -1 and ticket.assigned_to:
owner = ticket.assigned_to.id
f = FollowUp(ticket=ticket, date=timezone.now(), comment=comment,
time_spent=time_spent)
if is_helpdesk_staff(user):
f.user = user
f.public = public
reassigned = False
old_owner = ticket.assigned_to
if owner != -1:
if owner != 0 and ((ticket.assigned_to and owner != ticket.assigned_to.id) or not ticket.assigned_to):
new_user = User.objects.get(id=owner)
f.title = _('Assigned to %(username)s') % {
'username': new_user.get_username(),
}
ticket.assigned_to = new_user
reassigned = True
# user changed owner to 'unassign'
elif owner == 0 and ticket.assigned_to is not None:
f.title = _('Unassigned')
ticket.assigned_to = None
old_status_str, old_status = get_and_set_ticket_status(new_status, ticket, f)
files = process_attachments(f, files) if files else []
if title and title != ticket.title:
c = TicketChange(
followup=f,
field=_('Title'),
old_value=ticket.title,
new_value=title,
)
c.save()
ticket.title = title
if new_status != old_status:
c = TicketChange(
followup=f,
field=_('Status'),
old_value=old_status_str,
new_value=ticket.get_status_display(),
)
c.save()
if ticket.assigned_to != old_owner:
c = TicketChange(
followup=f,
field=_('Owner'),
old_value=old_owner,
new_value=ticket.assigned_to,
)
c.save()
if priority != ticket.priority:
c = TicketChange(
followup=f,
field=_('Priority'),
old_value=ticket.priority,
new_value=priority,
)
c.save()
ticket.priority = priority
if due_date != ticket.due_date:
c = TicketChange(
followup=f,
field=_('Due on'),
old_value=ticket.due_date,
new_value=due_date,
)
c.save()
ticket.due_date = due_date
for checklist in ticket.checklists.all():
new_completed_tasks = new_checklists[checklist.id]
for task in checklist.tasks.all():
changed = None
# Add completion if it was not done yet
if not task.completion_date and task.id in new_completed_tasks:
task.completion_date = timezone.now()
changed = 'completed'
# Remove it if it was done before
elif task.completion_date and task.id not in new_completed_tasks:
task.completion_date = None
changed = 'uncompleted'
# Save and add ticket change if task state has changed
if changed:
task.save(update_fields=['completion_date'])
f.ticketchange_set.create(
field=f'[{checklist.name}] {task.description}',
old_value=_('To do') if changed == 'completed' else _('Completed'),
new_value=_('Completed') if changed == 'completed' else _('To do'),
)
if new_status in (
Ticket.RESOLVED_STATUS, Ticket.CLOSED_STATUS
) and (
new_status == Ticket.RESOLVED_STATUS or ticket.resolution is None
):
ticket.resolution = comment
# ticket might have changed above, so we re-instantiate context with the
# (possibly) updated ticket.
context = safe_template_context(ticket)
context.update(
resolution=ticket.resolution,
comment=f.comment,
)
messages_sent_to = set()
try:
messages_sent_to.add(user.email)
except AttributeError:
pass
ticket = update_messages_sent_to_by_public_and_status(
public,
ticket,
f,
context,
messages_sent_to,
files
)
template_staff, template_cc = get_template_staff_and_template_cc(reassigned, f)
if ticket.assigned_to and (
ticket.assigned_to.usersettings_helpdesk.email_on_ticket_change
or (reassigned and ticket.assigned_to.usersettings_helpdesk.email_on_ticket_assign)
):
messages_sent_to.update(ticket.send(
{'assigned_to': (template_staff, context)},
dont_send_to=messages_sent_to,
fail_silently=True,
files=files,
))
messages_sent_to.update(ticket.send(
{'ticket_cc': (template_cc, context)},
dont_send_to=messages_sent_to,
fail_silently=True,
files=files,
))
ticket.save()
# auto subscribe user if enabled
add_staff_subscription(user, ticket)

View File

@ -64,7 +64,7 @@ urlpatterns = [
),
path("tickets/<int:ticket_id>/edit/", staff.edit_ticket, name="edit"),
path("tickets/<int:ticket_id>/update/",
staff.update_ticket, name="update"),
staff.update_ticket_view, name="update"),
path("tickets/<int:ticket_id>/delete/",
staff.delete_ticket, name="delete"),
path("tickets/<int:ticket_id>/hold/", staff.hold_ticket, name="hold"),

View File

@ -210,7 +210,7 @@ def view_ticket(request):
return HttpResponseRedirect(redirect_url)
if 'close' in request.GET and ticket.status == Ticket.RESOLVED_STATUS:
from helpdesk.views.staff import update_ticket
from helpdesk.views.staff import update_ticket_view
# Trick the update_ticket() view into thinking it's being called with
# a valid POST.
@ -224,7 +224,7 @@ def view_ticket(request):
request.POST['owner'] = ticket.assigned_to.id
request.GET = {}
return update_ticket(request, ticket_id, public=True)
return update_ticket_view(request, ticket_id, public=True)
# redirect user back to this ticket if possible.
redirect_url = ''

View File

@ -17,7 +17,7 @@ from django.contrib.auth import get_user_model
from django.contrib.auth.decorators import user_passes_test
from django.contrib.auth.views import redirect_to_login
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import PermissionDenied, ValidationError
from django.core.exceptions import PermissionDenied
from django.core.handlers.wsgi import WSGIRequest
from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator
from django.db.models import Q
@ -56,7 +56,7 @@ from helpdesk.forms import (
TicketForm,
UserSettingsForm
)
from helpdesk.lib import process_attachments, queue_template_context, safe_template_context
from helpdesk.lib import queue_template_context, safe_template_context
from helpdesk.models import (
Checklist,
ChecklistTask,
@ -70,13 +70,13 @@ from helpdesk.models import (
SavedSearch,
Ticket,
TicketCC,
TicketChange,
TicketCustomFieldValue,
TicketDependency,
UserSettings
)
from helpdesk.query import get_query_class, query_from_base64, query_to_base64
from helpdesk.user import HelpdeskUser
from helpdesk.update_ticket import update_ticket, subscribe_to_ticket_updates, return_ticketccstring_and_show_subscribe
import helpdesk.views.abstract_views as abstract_views
from helpdesk.views.permissions import MustBeStaffMixin
import json
@ -357,7 +357,7 @@ def view_ticket(request, ticket_id):
'title': ticket.title,
'comment': ''
}
return update_ticket(request, ticket_id)
return update_ticket_view(request, ticket_id)
if 'subscribe' in request.GET:
# Allow the user to subscribe him/herself to the ticket whilst viewing
@ -386,7 +386,7 @@ def view_ticket(request, ticket_id):
'comment': _('Accepted resolution and closed ticket'),
}
return update_ticket(request, ticket_id)
return update_ticket_view(request, ticket_id)
if helpdesk_settings.HELPDESK_STAFF_ONLY_TICKET_OWNERS:
users = User.objects.filter(
@ -491,68 +491,6 @@ def delete_ticket_checklist(request, ticket_id, checklist_id):
})
def return_ticketccstring_and_show_subscribe(user, ticket):
"""used in view_ticket() and followup_edit()"""
# create the ticketcc_string and check whether current user is already
# subscribed
username = user.get_username().upper()
try:
useremail = user.email.upper()
except AttributeError:
useremail = ""
strings_to_check = list()
strings_to_check.append(username)
strings_to_check.append(useremail)
ticketcc_string = ''
all_ticketcc = ticket.ticketcc_set.all()
counter_all_ticketcc = len(all_ticketcc) - 1
show_subscribe = True
for i, ticketcc in enumerate(all_ticketcc):
ticketcc_this_entry = str(ticketcc.display)
ticketcc_string += ticketcc_this_entry
if i < counter_all_ticketcc:
ticketcc_string += ', '
if strings_to_check.__contains__(ticketcc_this_entry.upper()):
show_subscribe = False
# check whether current user is a submitter or assigned to ticket
assignedto_username = str(ticket.assigned_to).upper()
strings_to_check = list()
if ticket.submitter_email is not None:
submitter_email = ticket.submitter_email.upper()
strings_to_check.append(submitter_email)
strings_to_check.append(assignedto_username)
if strings_to_check.__contains__(username) or strings_to_check.__contains__(useremail):
show_subscribe = False
return ticketcc_string, show_subscribe
def subscribe_to_ticket_updates(ticket, user=None, email=None, can_view=True, can_update=False):
if ticket is not None:
queryset = TicketCC.objects.filter(
ticket=ticket, user=user, email=email)
# Don't create duplicate entries for subscribers
if queryset.count() > 0:
return queryset.first()
if user is None and len(email) < 5:
raise ValidationError(
_('When you add somebody on Cc, you must provide either a User or a valid email. Email: %s' % email)
)
return ticket.ticketcc_set.create(
user=user,
email=email,
can_view=can_view,
can_update=can_update
)
def get_ticket_from_request_with_authorisation(
request: WSGIRequest,
ticket_id: str,
@ -614,38 +552,6 @@ def get_due_date_from_request_or_ticket(
return due_date
def get_and_set_ticket_status(
new_status: int,
ticket: Ticket,
follow_up: FollowUp
) -> typing.Tuple[str, int]:
"""Performs comparision on previous status to new status,
updating the title as required.
Returns:
The old status as a display string, old status code string
"""
old_status_str = ticket.get_status_display()
old_status = ticket.status
if new_status != ticket.status:
ticket.status = new_status
ticket.save()
follow_up.new_status = new_status
if follow_up.title:
follow_up.title += ' and %s' % ticket.get_status_display()
else:
follow_up.title = '%s' % ticket.get_status_display()
if not follow_up.title:
if follow_up.comment:
follow_up.title = _('Comment')
else:
follow_up.title = _('Updated')
follow_up.save()
return old_status_str, old_status
def get_time_spent_from_request(request: WSGIRequest) -> typing.Optional[timedelta]:
if request.POST.get("time_spent"):
(hours, minutes) = [int(f)
@ -654,99 +560,24 @@ def get_time_spent_from_request(request: WSGIRequest) -> typing.Optional[timedel
return None
def update_messages_sent_to_by_public_and_status(
public: bool,
ticket: Ticket,
follow_up: FollowUp,
context: str,
messages_sent_to: typing.Set[str],
files: typing.List[typing.Tuple[str, str]]
) -> Ticket:
"""Sets the status of the ticket"""
if public and (
follow_up.comment or (
follow_up.new_status in (
Ticket.RESOLVED_STATUS,
Ticket.CLOSED_STATUS
)
)
):
if follow_up.new_status == Ticket.RESOLVED_STATUS:
template = 'resolved_'
elif follow_up.new_status == Ticket.CLOSED_STATUS:
template = 'closed_'
else:
template = 'updated_'
roles = {
'submitter': (template + 'submitter', context),
'ticket_cc': (template + 'cc', context),
}
if ticket.assigned_to and ticket.assigned_to.usersettings_helpdesk.email_on_ticket_change:
roles['assigned_to'] = (template + 'cc', context)
messages_sent_to.update(
ticket.send(
roles,
dont_send_to=messages_sent_to,
fail_silently=True,
files=files
)
)
return ticket
def add_staff_subscription(
request: WSGIRequest,
ticket: Ticket
) -> None:
"""Auto subscribe the staff member if that's what the settigs say and the
user is authenticated and a staff member"""
if helpdesk_settings.HELPDESK_AUTO_SUBSCRIBE_ON_TICKET_RESPONSE \
and request.user.is_authenticated \
and return_ticketccstring_and_show_subscribe(request.user, ticket)[1]:
subscribe_to_ticket_updates(ticket, request.user)
def get_template_staff_and_template_cc(
reassigned, follow_up: FollowUp
) -> typing.Tuple[str, str]:
if reassigned:
template_staff = 'assigned_owner'
elif follow_up.new_status == Ticket.RESOLVED_STATUS:
template_staff = 'resolved_owner'
elif follow_up.new_status == Ticket.CLOSED_STATUS:
template_staff = 'closed_owner'
else:
template_staff = 'updated_owner'
if reassigned:
template_cc = 'assigned_cc'
elif follow_up.new_status == Ticket.RESOLVED_STATUS:
template_cc = 'resolved_cc'
elif follow_up.new_status == Ticket.CLOSED_STATUS:
template_cc = 'closed_cc'
else:
template_cc = 'updated_cc'
return template_staff, template_cc
def update_ticket(request, ticket_id, public=False):
def update_ticket_view(request, ticket_id, public=False):
ticket = get_ticket_from_request_with_authorisation(request, ticket_id, public)
comment = request.POST.get('comment', '')
new_status = int(request.POST.get('new_status', ticket.status))
title = request.POST.get('title', '')
public = request.POST.get('public', False)
owner = int(request.POST.get('owner', -1))
priority = int(request.POST.get('priority', ticket.priority))
# Check if a change happened on checklists
new_checklists = {}
changes_in_checklists = False
for checklist in ticket.checklists.all():
old_completed_id = sorted(list(checklist.tasks.completed().values_list('id', flat=True)))
new_completed_id = sorted(list(map(int, request.POST.getlist(f'checklist-{checklist.id}', []))))
if old_completed_id != new_completed_id:
old_completed = set(checklist.tasks.completed().values_list('id', flat=True))
new_checklist = set(map(int, request.POST.getlist(f'checklist-{checklist.id}', [])))
new_checklists[checklist.id] = new_checklist
if new_checklist != old_completed:
changes_in_checklists = True
time_spent = get_time_spent_from_request(request)
@ -768,180 +599,21 @@ def update_ticket(request, ticket_id, public=False):
if no_changes:
return return_to_ticket(request.user, helpdesk_settings, ticket)
# We need to allow the 'ticket' and 'queue' contexts to be applied to the
# comment.
context = safe_template_context(ticket)
from django.template import engines
template_func = engines['django'].from_string
# this prevents system from trying to render any template tags
# broken into two stages to prevent changes from first replace being themselves
# changed by the second replace due to conflicting syntax
comment = comment.replace(
'{%', 'X-HELPDESK-COMMENT-VERBATIM').replace('%}', 'X-HELPDESK-COMMENT-ENDVERBATIM')
comment = comment.replace(
'X-HELPDESK-COMMENT-VERBATIM', '{% verbatim %}{%'
).replace(
'X-HELPDESK-COMMENT-ENDVERBATIM', '%}{% endverbatim %}'
)
# render the neutralized template
comment = template_func(comment).render(context)
if owner == -1 and ticket.assigned_to:
owner = ticket.assigned_to.id
f = FollowUp(ticket=ticket, date=timezone.now(), comment=comment,
time_spent=time_spent)
if is_helpdesk_staff(request.user):
f.user = request.user
f.public = public
reassigned = False
old_owner = ticket.assigned_to
if owner != -1:
if owner != 0 and ((ticket.assigned_to and owner != ticket.assigned_to.id) or not ticket.assigned_to):
new_user = User.objects.get(id=owner)
f.title = _('Assigned to %(username)s') % {
'username': new_user.get_username(),
}
ticket.assigned_to = new_user
reassigned = True
# user changed owner to 'unassign'
elif owner == 0 and ticket.assigned_to is not None:
f.title = _('Unassigned')
ticket.assigned_to = None
old_status_str, old_status = get_and_set_ticket_status(new_status, ticket, f)
files = process_attachments(f, request.FILES.getlist('attachment')) if request.FILES else []
if title and title != ticket.title:
c = TicketChange(
followup=f,
field=_('Title'),
old_value=ticket.title,
new_value=title,
)
c.save()
ticket.title = title
if new_status != old_status:
c = TicketChange(
followup=f,
field=_('Status'),
old_value=old_status_str,
new_value=ticket.get_status_display(),
)
c.save()
if ticket.assigned_to != old_owner:
c = TicketChange(
followup=f,
field=_('Owner'),
old_value=old_owner,
new_value=ticket.assigned_to,
)
c.save()
if priority != ticket.priority:
c = TicketChange(
followup=f,
field=_('Priority'),
old_value=ticket.priority,
new_value=priority,
)
c.save()
ticket.priority = priority
if due_date != ticket.due_date:
c = TicketChange(
followup=f,
field=_('Due on'),
old_value=ticket.due_date,
new_value=due_date,
)
c.save()
ticket.due_date = due_date
if changes_in_checklists:
for checklist in ticket.checklists.all():
new_completed_tasks = list(map(int, request.POST.getlist(f'checklist-{checklist.id}', [])))
for task in checklist.tasks.all():
changed = None
# Add completion if it was not done yet
if not task.completion_date and task.id in new_completed_tasks:
task.completion_date = timezone.now()
changed = 'completed'
# Remove it if it was done before
elif task.completion_date and task.id not in new_completed_tasks:
task.completion_date = None
changed = 'uncompleted'
# Save and add ticket change if task state has changed
if changed:
task.save(update_fields=['completion_date'])
f.ticketchange_set.create(
field=f'[{checklist.name}] {task.description}',
old_value=_('To do') if changed == 'completed' else _('Completed'),
new_value=_('Completed') if changed == 'completed' else _('To do'),
)
if new_status in (
Ticket.RESOLVED_STATUS, Ticket.CLOSED_STATUS
) and (
new_status == Ticket.RESOLVED_STATUS or ticket.resolution is None
):
ticket.resolution = comment
# ticket might have changed above, so we re-instantiate context with the
# (possibly) updated ticket.
context = safe_template_context(ticket)
context.update(
resolution=ticket.resolution,
comment=f.comment,
)
messages_sent_to = set()
try:
messages_sent_to.add(request.user.email)
except AttributeError:
pass
ticket = update_messages_sent_to_by_public_and_status(
public,
update_ticket(
request.user,
ticket,
f,
context,
messages_sent_to,
files
title = title,
comment = comment,
files = request.FILES.getlist('attachment'),
public = request.POST.get('public', False),
owner = int(request.POST.get('owner', -1)),
priority = int(request.POST.get('priority', -1)),
new_status = new_status,
time_spent = get_time_spent_from_request(request),
due_date = get_due_date_from_request_or_ticket(request, ticket),
new_checklists = new_checklists,
)
template_staff, template_cc = get_template_staff_and_template_cc(reassigned, f)
if ticket.assigned_to and (
ticket.assigned_to.usersettings_helpdesk.email_on_ticket_change
or (reassigned and ticket.assigned_to.usersettings_helpdesk.email_on_ticket_assign)
):
messages_sent_to.update(ticket.send(
{'assigned_to': (template_staff, context)},
dont_send_to=messages_sent_to,
fail_silently=True,
files=files,
))
messages_sent_to.update(ticket.send(
{'ticket_cc': (template_cc, context)},
dont_send_to=messages_sent_to,
fail_silently=True,
files=files,
))
ticket.save()
# auto subscribe user if enabled
add_staff_subscription(request, ticket)
return return_to_ticket(request.user, helpdesk_settings, ticket)