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>/edit/", staff.edit_ticket, name="edit"),
path("tickets/<int:ticket_id>/update/", path("tickets/<int:ticket_id>/update/",
staff.update_ticket, name="update"), staff.update_ticket_view, name="update"),
path("tickets/<int:ticket_id>/delete/", path("tickets/<int:ticket_id>/delete/",
staff.delete_ticket, name="delete"), staff.delete_ticket, name="delete"),
path("tickets/<int:ticket_id>/hold/", staff.hold_ticket, name="hold"), 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) return HttpResponseRedirect(redirect_url)
if 'close' in request.GET and ticket.status == Ticket.RESOLVED_STATUS: 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 # Trick the update_ticket() view into thinking it's being called with
# a valid POST. # a valid POST.
@ -224,7 +224,7 @@ def view_ticket(request):
request.POST['owner'] = ticket.assigned_to.id request.POST['owner'] = ticket.assigned_to.id
request.GET = {} 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 user back to this ticket if possible.
redirect_url = '' 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.decorators import user_passes_test
from django.contrib.auth.views import redirect_to_login from django.contrib.auth.views import redirect_to_login
from django.contrib.contenttypes.models import ContentType 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.handlers.wsgi import WSGIRequest
from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator
from django.db.models import Q from django.db.models import Q
@ -56,7 +56,7 @@ from helpdesk.forms import (
TicketForm, TicketForm,
UserSettingsForm 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 ( from helpdesk.models import (
Checklist, Checklist,
ChecklistTask, ChecklistTask,
@ -70,13 +70,13 @@ from helpdesk.models import (
SavedSearch, SavedSearch,
Ticket, Ticket,
TicketCC, TicketCC,
TicketChange,
TicketCustomFieldValue, TicketCustomFieldValue,
TicketDependency, TicketDependency,
UserSettings UserSettings
) )
from helpdesk.query import get_query_class, query_from_base64, query_to_base64 from helpdesk.query import get_query_class, query_from_base64, query_to_base64
from helpdesk.user import HelpdeskUser 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 import helpdesk.views.abstract_views as abstract_views
from helpdesk.views.permissions import MustBeStaffMixin from helpdesk.views.permissions import MustBeStaffMixin
import json import json
@ -357,7 +357,7 @@ def view_ticket(request, ticket_id):
'title': ticket.title, 'title': ticket.title,
'comment': '' 'comment': ''
} }
return update_ticket(request, ticket_id) return update_ticket_view(request, ticket_id)
if 'subscribe' in request.GET: if 'subscribe' in request.GET:
# Allow the user to subscribe him/herself to the ticket whilst viewing # 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'), '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: if helpdesk_settings.HELPDESK_STAFF_ONLY_TICKET_OWNERS:
users = User.objects.filter( 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( def get_ticket_from_request_with_authorisation(
request: WSGIRequest, request: WSGIRequest,
ticket_id: str, ticket_id: str,
@ -614,38 +552,6 @@ def get_due_date_from_request_or_ticket(
return due_date 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]: def get_time_spent_from_request(request: WSGIRequest) -> typing.Optional[timedelta]:
if request.POST.get("time_spent"): if request.POST.get("time_spent"):
(hours, minutes) = [int(f) (hours, minutes) = [int(f)
@ -654,99 +560,24 @@ def get_time_spent_from_request(request: WSGIRequest) -> typing.Optional[timedel
return None return None
def update_messages_sent_to_by_public_and_status( def update_ticket_view(request, ticket_id, public=False):
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):
ticket = get_ticket_from_request_with_authorisation(request, ticket_id, public) ticket = get_ticket_from_request_with_authorisation(request, ticket_id, public)
comment = request.POST.get('comment', '') comment = request.POST.get('comment', '')
new_status = int(request.POST.get('new_status', ticket.status)) new_status = int(request.POST.get('new_status', ticket.status))
title = request.POST.get('title', '') title = request.POST.get('title', '')
public = request.POST.get('public', False)
owner = int(request.POST.get('owner', -1)) owner = int(request.POST.get('owner', -1))
priority = int(request.POST.get('priority', ticket.priority)) priority = int(request.POST.get('priority', ticket.priority))
# Check if a change happened on checklists # Check if a change happened on checklists
new_checklists = {}
changes_in_checklists = False changes_in_checklists = False
for checklist in ticket.checklists.all(): for checklist in ticket.checklists.all():
old_completed_id = sorted(list(checklist.tasks.completed().values_list('id', flat=True))) old_completed = set(checklist.tasks.completed().values_list('id', flat=True))
new_completed_id = sorted(list(map(int, request.POST.getlist(f'checklist-{checklist.id}', [])))) new_checklist = set(map(int, request.POST.getlist(f'checklist-{checklist.id}', [])))
if old_completed_id != new_completed_id: new_checklists[checklist.id] = new_checklist
if new_checklist != old_completed:
changes_in_checklists = True changes_in_checklists = True
time_spent = get_time_spent_from_request(request) time_spent = get_time_spent_from_request(request)
@ -768,180 +599,21 @@ def update_ticket(request, ticket_id, public=False):
if no_changes: if no_changes:
return return_to_ticket(request.user, helpdesk_settings, ticket) return return_to_ticket(request.user, helpdesk_settings, ticket)
# We need to allow the 'ticket' and 'queue' contexts to be applied to the update_ticket(
# comment. request.user,
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,
ticket, ticket,
f, title = title,
context, comment = comment,
messages_sent_to, files = request.FILES.getlist('attachment'),
files 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) return return_to_ticket(request.user, helpdesk_settings, ticket)