2008-02-06 05:36:07 +01:00
"""
2011-01-26 00:08:41 +01:00
django - helpdesk - A Django powered ticket tracker for small enterprise .
2007-12-27 01:29:17 +01:00
2008-02-06 05:36:07 +01:00
( c ) Copyright 2008 Jutda . All Rights Reserved . See LICENSE for details .
2007-12-27 01:29:17 +01:00
2008-08-19 10:50:38 +02:00
views / staff . py - The bulk of the application - provides most business logic and
2008-05-21 23:16:44 +02:00
renders all staff - facing views .
2007-12-27 01:29:17 +01:00
"""
2008-08-19 10:50:38 +02:00
2012-08-07 15:51:52 +02:00
from datetime import datetime , timedelta
2011-09-01 13:01:03 +02:00
import sys
2007-12-27 01:29:17 +01:00
2009-01-20 09:12:05 +01:00
from django . conf import settings
2014-06-18 16:21:37 +02:00
try :
from django . contrib . auth import get_user_model
User = get_user_model ( )
except ImportError :
from django . contrib . auth . models import User
2008-10-25 00:52:34 +02:00
from django . contrib . auth . decorators import login_required , user_passes_test
2008-08-13 23:42:57 +02:00
from django . core . files . base import ContentFile
2008-01-15 01:20:00 +01:00
from django . core . urlresolvers import reverse
2012-01-17 13:14:21 +01:00
from django . core . exceptions import ValidationError
2009-08-18 14:12:35 +02:00
from django . core import paginator
2008-08-19 10:50:38 +02:00
from django . db import connection
2007-12-27 01:29:17 +01:00
from django . db . models import Q
2009-07-22 10:19:46 +02:00
from django . http import HttpResponseRedirect , Http404 , HttpResponse , HttpResponseForbidden
2008-01-15 01:20:00 +01:00
from django . shortcuts import render_to_response , get_object_or_404
from django . template import loader , Context , RequestContext
2014-07-21 09:12:27 +02:00
from django . utils . dates import MONTHS_3
2008-05-07 11:04:18 +02:00
from django . utils . translation import ugettext as _
2011-01-29 07:02:03 +01:00
from django . utils . html import escape
2011-11-28 15:17:54 +01:00
from django import forms
2007-12-27 01:29:17 +01:00
2013-01-23 01:35:18 +01:00
try :
from django . utils import timezone
2013-01-23 01:55:36 +01:00
except ImportError :
2013-01-23 01:35:18 +01:00
from datetime import datetime as timezone
2011-05-10 11:27:11 +02:00
from helpdesk . forms import TicketForm , UserSettingsForm , EmailIgnoreForm , EditTicketForm , TicketCCForm , EditFollowUpForm , TicketDependencyForm
2011-05-11 12:07:46 +02:00
from helpdesk . lib import send_templated_mail , query_to_dict , apply_query , safe_template_context
2011-05-10 11:27:11 +02:00
from helpdesk . models import Ticket , Queue , FollowUp , TicketChange , PreSetReply , Attachment , SavedSearch , IgnoreEmail , TicketCC , TicketDependency
2011-11-19 09:34:07 +01:00
from helpdesk import settings as helpdesk_settings
2014-07-20 10:36:24 +02:00
if helpdesk_settings . HELPDESK_CUSTOM_STAFF_FILTER_CALLBACK :
2014-07-28 06:47:19 +02:00
# apply a custom user validation condition
2014-07-20 10:36:24 +02:00
staff_member_required = user_passes_test ( helpdesk_settings . HELPDESK_CUSTOM_STAFF_FILTER_CALLBACK )
elif helpdesk_settings . HELPDESK_ALLOW_NON_STAFF_TICKET_UPDATE :
2011-11-19 09:34:07 +01:00
# treat 'normal' users like 'staff'
staff_member_required = user_passes_test ( lambda u : u . is_authenticated ( ) and u . is_active )
else :
2012-07-23 09:08:26 +02:00
try :
from django . contrib . admin . views . decorators import staff_member_required
except :
staff_member_required = user_passes_test ( lambda u : u . is_authenticated ( ) and u . is_active and u . is_staff )
2011-11-19 09:34:07 +01:00
2008-10-25 00:52:34 +02:00
2011-11-19 09:34:07 +01:00
superuser_required = user_passes_test ( lambda u : u . is_authenticated ( ) and u . is_active and u . is_superuser )
2007-12-27 01:29:17 +01:00
2008-08-19 10:50:38 +02:00
2007-12-27 01:29:17 +01:00
def dashboard ( request ) :
2008-01-16 01:26:24 +01:00
"""
2008-05-21 23:16:44 +02:00
A quick summary overview for users : A list of their own tickets , a table
2008-08-19 10:50:38 +02:00
showing ticket counts by queue / status , and a list of unassigned tickets
2008-05-21 23:16:44 +02:00
with options for them to ' Take ' ownership of said tickets .
2008-01-16 01:26:24 +01:00
"""
2011-11-29 14:08:08 +01:00
# open & reopened tickets, assigned to current user
2014-07-23 01:37:21 +02:00
tickets = Ticket . objects . select_related ( ' queue ' ) . filter (
2008-08-20 03:57:41 +02:00
assigned_to = request . user ,
) . exclude (
2011-11-19 09:34:07 +01:00
status__in = [ Ticket . CLOSED_STATUS , Ticket . RESOLVED_STATUS ] ,
2008-08-20 03:57:41 +02:00
)
2008-08-19 10:50:38 +02:00
2011-11-29 14:08:08 +01:00
# closed & resolved tickets, assigned to current user
2014-07-23 01:37:21 +02:00
tickets_closed_resolved = Ticket . objects . select_related ( ' queue ' ) . filter (
2014-07-20 10:36:24 +02:00
assigned_to = request . user ,
2011-11-19 09:34:07 +01:00
status__in = [ Ticket . CLOSED_STATUS , Ticket . RESOLVED_STATUS ] )
2014-07-23 01:37:21 +02:00
unassigned_tickets = Ticket . objects . select_related ( ' queue ' ) . filter (
2008-08-20 03:57:41 +02:00
assigned_to__isnull = True ,
) . exclude (
status = Ticket . CLOSED_STATUS ,
)
2008-08-19 10:50:38 +02:00
2011-11-29 14:08:08 +01:00
# all tickets, reported by current user
all_tickets_reported_by_current_user = ' '
email_current_user = request . user . email
if email_current_user :
2014-07-23 01:37:21 +02:00
all_tickets_reported_by_current_user = Ticket . objects . select_related ( ' queue ' ) . filter (
2011-11-29 14:08:08 +01:00
submitter_email = email_current_user ,
) . order_by ( ' status ' )
2014-01-29 12:13:42 +01:00
basic_ticket_stats = calc_basic_ticket_stats ( Ticket )
2011-11-29 14:08:08 +01:00
2008-08-19 10:50:38 +02:00
# The following query builds a grid of queues & ticket statuses,
# to be displayed to the user. EG:
# Open Resolved
# Queue 1 10 4
# Queue 2 4 12
2008-05-21 23:16:44 +02:00
cursor = connection . cursor ( )
2014-01-29 12:09:58 +01:00
cursor . execute ( """
SELECT q . id as queue ,
q . title AS name ,
COUNT ( CASE t . status WHEN ' 1 ' THEN t . id WHEN ' 2 ' THEN t . id END ) AS open ,
COUNT ( CASE t . status WHEN ' 3 ' THEN t . id END ) AS resolved ,
COUNT ( CASE t . status WHEN ' 4 ' THEN t . id END ) AS closed
FROM helpdesk_ticket t ,
helpdesk_queue q
WHERE q . id = t . queue_id
GROUP BY queue , name
ORDER BY q . id ;
""" )
2014-07-20 10:36:24 +02:00
2008-05-21 23:16:44 +02:00
dash_tickets = query_to_dict ( cursor . fetchall ( ) , cursor . description )
return render_to_response ( ' helpdesk/dashboard.html ' ,
RequestContext ( request , {
' user_tickets ' : tickets ,
2011-11-19 09:34:07 +01:00
' user_tickets_closed_resolved ' : tickets_closed_resolved ,
2008-05-21 23:16:44 +02:00
' unassigned_tickets ' : unassigned_tickets ,
2011-11-29 14:08:08 +01:00
' all_tickets_reported_by_current_user ' : all_tickets_reported_by_current_user ,
2008-05-21 23:16:44 +02:00
' dash_tickets ' : dash_tickets ,
2012-08-07 15:51:52 +02:00
' basic_ticket_stats ' : basic_ticket_stats ,
2008-05-21 23:16:44 +02:00
} ) )
2009-01-11 01:26:00 +01:00
dashboard = staff_member_required ( dashboard )
2007-12-27 01:29:17 +01:00
2008-08-19 10:50:38 +02:00
2008-01-15 05:00:19 +01:00
def delete_ticket ( request , ticket_id ) :
ticket = get_object_or_404 ( Ticket , id = ticket_id )
2008-08-19 10:50:38 +02:00
2008-01-15 05:00:19 +01:00
if request . method == ' GET ' :
return render_to_response ( ' helpdesk/delete_ticket.html ' ,
RequestContext ( request , {
' ticket ' : ticket ,
} ) )
else :
ticket . delete ( )
return HttpResponseRedirect ( reverse ( ' helpdesk_home ' ) )
2009-01-11 01:26:00 +01:00
delete_ticket = staff_member_required ( delete_ticket )
2008-01-15 05:00:19 +01:00
2012-08-07 15:32:45 +02:00
def followup_edit ( request , ticket_id , followup_id ) :
2011-01-29 07:02:03 +01:00
" Edit followup options with an ability to change the ticket. "
followup = get_object_or_404 ( FollowUp , id = followup_id )
ticket = get_object_or_404 ( Ticket , id = ticket_id )
if request . method == ' GET ' :
form = EditFollowUpForm ( initial =
{ ' title ' : escape ( followup . title ) ,
' ticket ' : followup . ticket ,
' comment ' : escape ( followup . comment ) ,
' public ' : followup . public ,
' new_status ' : followup . new_status ,
} )
2012-08-07 15:41:43 +02:00
ticketcc_string , SHOW_SUBSCRIBE = return_ticketccstring_and_show_subscribe ( request . user , ticket )
2011-01-29 07:02:03 +01:00
return render_to_response ( ' helpdesk/followup_edit.html ' ,
RequestContext ( request , {
' followup ' : followup ,
' ticket ' : ticket ,
' form ' : form ,
2012-08-07 15:41:43 +02:00
' ticketcc_string ' : ticketcc_string ,
2011-01-29 07:02:03 +01:00
} ) )
elif request . method == ' POST ' :
form = EditFollowUpForm ( request . POST )
if form . is_valid ( ) :
title = form . cleaned_data [ ' title ' ]
_ticket = form . cleaned_data [ ' ticket ' ]
comment = form . cleaned_data [ ' comment ' ]
public = form . cleaned_data [ ' public ' ]
new_status = form . cleaned_data [ ' new_status ' ]
#will save previous date
old_date = followup . date
new_followup = FollowUp ( title = title , date = old_date , ticket = _ticket , comment = comment , public = public , new_status = new_status , )
2011-11-19 09:34:07 +01:00
# keep old user if one did exist before.
if followup . user :
new_followup . user = followup . user
2011-01-29 07:02:03 +01:00
new_followup . save ( )
2011-11-19 09:34:07 +01:00
# get list of old attachments & link them to new_followup
2012-08-07 15:32:45 +02:00
attachments = Attachment . objects . filter ( followup = followup )
2011-11-19 09:34:07 +01:00
for attachment in attachments :
attachment . followup = new_followup
attachment . save ( )
# delete old followup
2012-08-07 15:32:45 +02:00
followup . delete ( )
2011-01-29 07:02:03 +01:00
return HttpResponseRedirect ( reverse ( ' helpdesk_view ' , args = [ ticket . id ] ) )
2012-08-07 15:43:26 +02:00
followup_edit = staff_member_required ( followup_edit )
2012-08-07 15:32:45 +02:00
def followup_delete ( request , ticket_id , followup_id ) :
''' followup delete for superuser '''
ticket = get_object_or_404 ( Ticket , id = ticket_id )
if not request . user . is_superuser :
return HttpResponseRedirect ( reverse ( ' helpdesk_view ' , args = [ ticket . id ] ) )
followup = get_object_or_404 ( FollowUp , id = followup_id )
followup . delete ( )
return HttpResponseRedirect ( reverse ( ' helpdesk_view ' , args = [ ticket . id ] ) )
2012-08-07 15:43:26 +02:00
followup_delete = staff_member_required ( followup_delete )
2012-08-07 15:32:45 +02:00
2007-12-27 01:29:17 +01:00
def view_ticket ( request , ticket_id ) :
ticket = get_object_or_404 ( Ticket , id = ticket_id )
2008-08-19 10:50:38 +02:00
2007-12-27 01:29:17 +01:00
if request . GET . has_key ( ' take ' ) :
2008-08-19 10:50:38 +02:00
# Allow the user to assign the ticket to themselves whilst viewing it.
2014-07-20 10:36:24 +02:00
2011-09-05 20:40:53 +02:00
# Trick the update_ticket() view into thinking it's being called with
# a valid POST.
request . POST = {
' owner ' : request . user . id ,
' public ' : 1 ,
' title ' : ticket . title ,
' comment ' : ' '
}
return update_ticket ( request , ticket_id )
2008-01-15 01:20:00 +01:00
2012-08-07 15:41:43 +02:00
if request . GET . has_key ( ' subscribe ' ) :
# Allow the user to subscribe him/herself to the ticket whilst viewing it.
ticketcc_string , SHOW_SUBSCRIBE = return_ticketccstring_and_show_subscribe ( request . user , ticket )
if SHOW_SUBSCRIBE :
subscribe_staff_member_to_ticket ( ticket , request . user )
return HttpResponseRedirect ( reverse ( ' helpdesk_view ' , args = [ ticket . id ] ) )
2008-01-07 21:22:13 +01:00
if request . GET . has_key ( ' close ' ) and ticket . status == Ticket . RESOLVED_STATUS :
2008-08-19 10:50:38 +02:00
if not ticket . assigned_to :
2008-01-07 21:22:13 +01:00
owner = 0
else :
owner = ticket . assigned_to . id
2008-08-19 10:50:38 +02:00
# Trick the update_ticket() view into thinking it's being called with
# a valid POST.
request . POST = {
' new_status ' : Ticket . CLOSED_STATUS ,
' public ' : 1 ,
' owner ' : owner ,
' title ' : ticket . title ,
' comment ' : _ ( ' Accepted resolution and closed ticket ' ) ,
}
2008-01-07 21:22:13 +01:00
return update_ticket ( request , ticket_id )
2007-12-27 01:29:17 +01:00
2012-01-17 22:40:44 +01:00
if helpdesk_settings . HELPDESK_STAFF_ONLY_TICKET_OWNERS :
2014-07-15 23:02:31 +02:00
users = User . objects . filter ( is_active = True , is_staff = True ) . order_by ( User . USERNAME_FIELD )
2012-01-17 22:40:44 +01:00
else :
2014-07-15 23:02:31 +02:00
users = User . objects . filter ( is_active = True ) . order_by ( User . USERNAME_FIELD )
2012-01-17 22:40:44 +01:00
2012-01-21 00:03:07 +01:00
# TODO: shouldn't this template get a form to begin with?
form = TicketForm ( initial = { ' due_date ' : ticket . due_date } )
2012-08-07 15:41:43 +02:00
ticketcc_string , SHOW_SUBSCRIBE = return_ticketccstring_and_show_subscribe ( request . user , ticket )
2007-12-27 01:29:17 +01:00
return render_to_response ( ' helpdesk/ticket.html ' ,
RequestContext ( request , {
' ticket ' : ticket ,
2012-01-21 00:03:07 +01:00
' form ' : form ,
2012-01-17 22:40:44 +01:00
' active_users ' : users ,
2008-01-10 01:28:45 +01:00
' priorities ' : Ticket . PRIORITY_CHOICES ,
2008-01-11 02:00:01 +01:00
' preset_replies ' : PreSetReply . objects . filter ( Q ( queues = ticket . queue ) | Q ( queues__isnull = True ) ) ,
2012-08-07 15:41:43 +02:00
' ticketcc_string ' : ticketcc_string ,
' SHOW_SUBSCRIBE ' : SHOW_SUBSCRIBE ,
2007-12-27 01:29:17 +01:00
} ) )
2009-01-11 01:26:00 +01:00
view_ticket = staff_member_required ( view_ticket )
2007-12-27 01:29:17 +01:00
2012-08-07 15:41:43 +02:00
def return_ticketccstring_and_show_subscribe ( user , ticket ) :
''' used in view_ticket() and followup_edit() '''
2014-07-20 10:36:24 +02:00
# create the ticketcc_string and check whether current user is already
2012-08-07 15:41:43 +02:00
# subscribed
username = user . username . upper ( )
useremail = user . email . upper ( )
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_string + ticketcc_this_entry
if i < counter_all_ticketcc :
ticketcc_string = 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 ( )
submitter_email = ticket . submitter_email . upper ( )
strings_to_check = list ( )
strings_to_check . append ( assignedto_username )
strings_to_check . append ( submitter_email )
if strings_to_check . __contains__ ( username ) or strings_to_check . __contains__ ( useremail ) :
SHOW_SUBSCRIBE = False
return ticketcc_string , SHOW_SUBSCRIBE
def subscribe_staff_member_to_ticket ( ticket , user ) :
''' used in view_ticket() and update_ticket() '''
ticketcc = TicketCC ( )
ticketcc . ticket = ticket
ticketcc . user = user
ticketcc . can_view = True
ticketcc . can_update = True
ticketcc . save ( )
2008-08-19 10:50:38 +02:00
2009-07-22 10:19:46 +02:00
def update_ticket ( request , ticket_id , public = False ) :
2011-11-19 09:34:07 +01:00
if not ( public or ( request . user . is_authenticated ( ) and request . user . is_active and ( request . user . is_staff or helpdesk_settings . HELPDESK_ALLOW_NON_STAFF_TICKET_UPDATE ) ) ) :
2012-08-07 14:50:25 +02:00
return HttpResponseRedirect ( ' %s ?next= %s ' % ( reverse ( ' login ' ) , request . path ) )
2009-07-22 10:19:46 +02:00
2007-12-27 01:29:17 +01:00
ticket = get_object_or_404 ( Ticket , id = ticket_id )
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 )
2012-08-07 12:25:21 +02:00
owner = int ( request . POST . get ( ' owner ' , - 1 ) )
2008-01-10 01:28:45 +01:00
priority = int ( request . POST . get ( ' priority ' , ticket . priority ) )
2012-05-08 14:36:13 +02:00
due_date_year = int ( request . POST . get ( ' due_date_year ' , 0 ) )
due_date_month = int ( request . POST . get ( ' due_date_month ' , 0 ) )
due_date_day = int ( request . POST . get ( ' due_date_day ' , 0 ) )
2012-04-11 09:48:39 +02:00
if not ( due_date_year and due_date_month and due_date_day ) :
due_date = ticket . due_date
else :
2012-05-11 09:02:55 +02:00
if ticket . due_date :
due_date = ticket . due_date
else :
2013-01-23 00:59:12 +01:00
due_date = timezone . now ( )
2012-05-11 09:02:55 +02:00
due_date = due_date . replace ( due_date_year , due_date_month , due_date_day )
2008-08-19 10:50:38 +02:00
2012-08-07 15:22:39 +02:00
no_changes = all ( [
not request . FILES ,
not comment ,
new_status == ticket . status ,
title == ticket . title ,
priority == int ( ticket . priority ) ,
due_date == ticket . due_date ,
2012-08-08 06:31:51 +02:00
( owner == - 1 ) or ( not owner and not ticket . assigned_to ) or ( owner and User . objects . get ( id = owner ) == ticket . assigned_to ) ,
2012-08-07 15:22:39 +02:00
] )
if no_changes :
return return_to_ticket ( request . user , helpdesk_settings , ticket )
2008-08-29 11:11:02 +02:00
# We need to allow the 'ticket' and 'queue' contexts to be applied to the
# comment.
from django . template import loader , Context
2009-07-22 10:19:46 +02:00
context = safe_template_context ( ticket )
2011-11-19 09:34:07 +01:00
# this line sometimes creates problems if code is sent as a comment.
# if comment contains some django code, like "why does {% if bla %} crash",
# then the following line will give us a crash, since django expects {% if %}
# to be closed with an {% endif %} tag.
2009-07-22 10:19:46 +02:00
comment = loader . get_template_from_string ( comment ) . render ( Context ( context ) )
2008-08-29 11:11:02 +02:00
2012-08-07 12:25:21 +02:00
if owner is - 1 and ticket . assigned_to :
2007-12-27 01:29:17 +01:00
owner = ticket . assigned_to . id
2013-01-23 00:59:12 +01:00
f = FollowUp ( ticket = ticket , date = timezone . now ( ) , comment = comment )
2009-03-08 07:03:55 +01:00
2011-11-19 09:34:07 +01:00
if request . user . is_staff or helpdesk_settings . HELPDESK_ALLOW_NON_STAFF_TICKET_UPDATE :
2009-01-23 11:35:14 +01:00
f . user = request . user
2009-03-08 07:03:55 +01:00
2008-08-19 10:50:38 +02:00
f . public = public
2007-12-27 01:29:17 +01:00
2008-01-21 02:02:12 +01:00
reassigned = False
2012-08-07 12:25:21 +02:00
if owner is not - 1 :
2008-11-09 06:34:51 +01:00
if owner != 0 and ( ( ticket . assigned_to and owner != ticket . assigned_to . id ) or not ticket . assigned_to ) :
2007-12-27 01:29:17 +01:00
new_user = User . objects . get ( id = owner )
2008-08-19 10:50:38 +02:00
f . title = _ ( ' Assigned to %(username)s ' ) % {
' username ' : new_user . username ,
}
2007-12-27 01:29:17 +01:00
ticket . assigned_to = new_user
2008-01-21 02:02:12 +01:00
reassigned = True
2011-11-28 14:49:37 +01:00
# user changed owner to 'unassign'
elif owner == 0 and ticket . assigned_to is not None :
f . title = _ ( ' Unassigned ' )
ticket . assigned_to = None
2008-08-19 10:50:38 +02:00
2007-12-27 01:29:17 +01:00
if new_status != ticket . status :
ticket . status = new_status
ticket . save ( )
f . new_status = new_status
if f . title :
f . title + = ' and %s ' % ticket . get_status_display ( )
else :
f . title = ' %s ' % ticket . get_status_display ( )
if not f . title :
if f . comment :
2008-05-07 11:04:18 +02:00
f . title = _ ( ' Comment ' )
2007-12-27 01:29:17 +01:00
else :
2008-05-07 11:04:18 +02:00
f . title = _ ( ' Updated ' )
2007-12-27 01:29:17 +01:00
f . save ( )
2009-03-08 07:03:55 +01:00
2009-01-20 09:12:05 +01:00
files = [ ]
if request . FILES :
import mimetypes , os
for file in request . FILES . getlist ( ' attachment ' ) :
2013-01-20 04:45:33 +01:00
filename = file . name . encode ( ' ascii ' , ' ignore ' )
2009-01-20 09:12:05 +01:00
a = Attachment (
followup = f ,
filename = filename ,
mime_type = mimetypes . guess_type ( filename ) [ 0 ] or ' application/octet-stream ' ,
size = file . size ,
)
2013-01-20 04:45:33 +01:00
a . file . save ( filename , file , save = False )
2009-01-20 09:12:05 +01:00
a . save ( )
2009-03-08 07:03:55 +01:00
2009-01-20 09:12:05 +01:00
if file . size < getattr ( settings , ' MAX_EMAIL_ATTACHMENT_SIZE ' , 512000 ) :
2009-03-08 07:03:55 +01:00
# Only files smaller than 512kb (or as defined in
2009-01-20 09:12:05 +01:00
# settings.MAX_EMAIL_ATTACHMENT_SIZE) are sent via email.
files . append ( a . file . path )
2008-08-19 10:50:38 +02:00
2007-12-27 01:29:17 +01:00
if title != ticket . title :
2008-08-19 10:50:38 +02:00
c = TicketChange (
followup = f ,
field = _ ( ' Title ' ) ,
old_value = ticket . title ,
new_value = title ,
)
2007-12-27 01:29:17 +01:00
c . save ( )
ticket . title = title
2008-01-10 01:28:45 +01:00
if priority != ticket . priority :
2008-08-19 10:50:38 +02:00
c = TicketChange (
followup = f ,
field = _ ( ' Priority ' ) ,
old_value = ticket . priority ,
new_value = priority ,
)
2008-01-10 01:28:45 +01:00
c . save ( )
ticket . priority = priority
2012-01-21 00:03:07 +01:00
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
2012-01-12 18:21:25 +01:00
if new_status in [ Ticket . RESOLVED_STATUS , Ticket . CLOSED_STATUS ] :
2012-08-07 12:25:21 +02:00
if new_status == Ticket . RESOLVED_STATUS or ticket . resolution is None :
ticket . resolution = comment
2008-08-19 10:50:38 +02:00
2009-08-11 11:02:48 +02:00
messages_sent_to = [ ]
2014-07-20 10:36:24 +02:00
# ticket might have changed above, so we re-instantiate context with the
2011-02-08 22:19:17 +01:00
# (possibly) updated ticket.
context = safe_template_context ( ticket )
2009-08-18 14:12:35 +02:00
context . update (
resolution = ticket . resolution ,
comment = f . comment ,
)
2013-01-21 07:25:14 +01:00
if public and ( f . comment or ( f . new_status in ( Ticket . RESOLVED_STATUS , Ticket . CLOSED_STATUS ) ) ) :
2014-07-20 10:36:24 +02:00
2008-08-19 10:50:38 +02:00
2008-01-07 21:22:13 +01:00
if f . new_status == Ticket . RESOLVED_STATUS :
2013-04-19 08:54:24 +02:00
template = ' resolved_ '
2008-01-21 02:02:12 +01:00
elif f . new_status == Ticket . CLOSED_STATUS :
2013-04-19 08:54:24 +02:00
template = ' closed_ '
2008-01-07 21:22:13 +01:00
else :
2013-04-19 08:54:24 +02:00
template = ' updated_ '
template_suffix = ' submitter '
2008-08-19 10:50:38 +02:00
2013-01-21 07:25:14 +01:00
if ticket . submitter_email :
send_templated_mail (
2013-04-19 08:54:24 +02:00
template + template_suffix ,
2013-01-21 07:25:14 +01:00
context ,
recipients = ticket . submitter_email ,
sender = ticket . queue . from_address ,
fail_silently = True ,
files = files ,
)
messages_sent_to . append ( ticket . submitter_email )
2008-01-07 21:22:13 +01:00
2013-04-19 08:54:24 +02:00
template_suffix = ' cc '
2009-09-09 10:47:48 +02:00
for cc in ticket . ticketcc_set . all ( ) :
if cc . email_address not in messages_sent_to :
send_templated_mail (
2013-04-19 08:54:24 +02:00
template + template_suffix ,
2009-09-09 10:47:48 +02:00
context ,
recipients = cc . email_address ,
sender = ticket . queue . from_address ,
fail_silently = True ,
)
messages_sent_to . append ( cc . email_address )
2009-08-11 11:02:48 +02:00
if ticket . assigned_to and request . user != ticket . assigned_to and ticket . assigned_to . email and ticket . assigned_to . email not in messages_sent_to :
2008-08-19 10:50:38 +02:00
# We only send e-mails to staff members if the ticket is updated by
2009-07-22 10:19:46 +02:00
# another user. The actual template varies, depending on what has been
# changed.
2008-01-21 02:02:12 +01:00
if reassigned :
2008-04-02 01:26:12 +02:00
template_staff = ' assigned_owner '
2008-01-21 02:02:12 +01:00
elif f . new_status == Ticket . RESOLVED_STATUS :
2008-04-02 01:26:12 +02:00
template_staff = ' resolved_owner '
2008-01-21 02:02:12 +01:00
elif f . new_status == Ticket . CLOSED_STATUS :
2008-04-02 01:26:12 +02:00
template_staff = ' closed_owner '
2008-01-21 02:02:12 +01:00
else :
2008-04-02 01:26:12 +02:00
template_staff = ' updated_owner '
2008-08-19 10:50:38 +02:00
2009-07-22 10:19:46 +02:00
if ( not reassigned or ( reassigned and ticket . assigned_to . usersettings . settings . get ( ' email_on_ticket_assign ' , False ) ) ) or ( not reassigned and ticket . assigned_to . usersettings . settings . get ( ' email_on_ticket_change ' , False ) ) :
2008-09-09 10:32:01 +02:00
send_templated_mail (
template_staff ,
context ,
recipients = ticket . assigned_to . email ,
sender = ticket . queue . from_address ,
fail_silently = True ,
2009-01-20 09:12:05 +01:00
files = files ,
2008-09-09 10:32:01 +02:00
)
2009-08-11 11:02:48 +02:00
messages_sent_to . append ( ticket . assigned_to . email )
2008-08-19 10:50:38 +02:00
2009-08-11 11:02:48 +02:00
if ticket . queue . updated_ticket_cc and ticket . queue . updated_ticket_cc not in messages_sent_to :
2008-04-02 01:26:12 +02:00
if reassigned :
template_cc = ' assigned_cc '
elif f . new_status == Ticket . RESOLVED_STATUS :
template_cc = ' resolved_cc '
elif f . new_status == Ticket . CLOSED_STATUS :
template_cc = ' closed_cc '
else :
template_cc = ' updated_cc '
2008-08-19 10:50:38 +02:00
send_templated_mail (
template_cc ,
context ,
recipients = ticket . queue . updated_ticket_cc ,
sender = ticket . queue . from_address ,
fail_silently = True ,
2009-01-20 09:12:05 +01:00
files = files ,
2008-08-19 10:50:38 +02:00
)
2008-02-08 01:19:58 +01:00
2007-12-27 01:29:17 +01:00
ticket . save ( )
2008-08-19 10:50:38 +02:00
2012-08-07 15:41:43 +02:00
# auto subscribe user if enabled
2013-09-16 03:54:46 +02:00
if helpdesk_settings . HELPDESK_AUTO_SUBSCRIBE_ON_TICKET_RESPONSE and request . user . is_authenticated ( ) :
2012-08-07 15:41:43 +02:00
ticketcc_string , SHOW_SUBSCRIBE = return_ticketccstring_and_show_subscribe ( request . user , ticket )
if SHOW_SUBSCRIBE :
subscribe_staff_member_to_ticket ( ticket , request . user )
2012-08-07 15:22:39 +02:00
return return_to_ticket ( request . user , helpdesk_settings , ticket )
2012-08-07 15:41:43 +02:00
2012-08-07 15:22:39 +02:00
def return_to_ticket ( user , helpdesk_settings , ticket ) :
''' Helpder function for update_ticket '''
if user . is_staff or helpdesk_settings . HELPDESK_ALLOW_NON_STAFF_TICKET_UPDATE :
2009-01-23 11:35:14 +01:00
return HttpResponseRedirect ( ticket . get_absolute_url ( ) )
else :
return HttpResponseRedirect ( ticket . ticket_url )
2007-12-27 01:29:17 +01:00
2008-08-19 10:50:38 +02:00
2009-03-08 07:03:55 +01:00
def mass_update ( request ) :
tickets = request . POST . getlist ( ' ticket_id ' )
action = request . POST . get ( ' action ' , None )
if not ( tickets and action ) :
return HttpResponseRedirect ( reverse ( ' helpdesk_list ' ) )
if action . startswith ( ' assign_ ' ) :
parts = action . split ( ' _ ' )
user = User . objects . get ( id = parts [ 1 ] )
action = ' assign '
elif action == ' take ' :
user = request . user
action = ' assign '
for t in Ticket . objects . filter ( id__in = tickets ) :
if action == ' assign ' and t . assigned_to != user :
t . assigned_to = user
t . save ( )
2013-01-23 00:59:12 +01:00
f = FollowUp ( ticket = t , date = timezone . now ( ) , title = _ ( ' Assigned to %(username)s in bulk update ' % { ' username ' : user . username } ) , public = True , user = request . user )
2009-03-08 07:03:55 +01:00
f . save ( )
elif action == ' unassign ' and t . assigned_to is not None :
t . assigned_to = None
t . save ( )
2013-01-23 00:59:12 +01:00
f = FollowUp ( ticket = t , date = timezone . now ( ) , title = _ ( ' Unassigned in bulk update ' ) , public = True , user = request . user )
2009-03-08 07:03:55 +01:00
f . save ( )
elif action == ' close ' and t . status != Ticket . CLOSED_STATUS :
t . status = Ticket . CLOSED_STATUS
t . save ( )
2013-01-23 00:59:12 +01:00
f = FollowUp ( ticket = t , date = timezone . now ( ) , title = _ ( ' Closed in bulk update ' ) , public = False , user = request . user , new_status = Ticket . CLOSED_STATUS )
2009-03-08 07:03:55 +01:00
f . save ( )
elif action == ' close_public ' and t . status != Ticket . CLOSED_STATUS :
t . status = Ticket . CLOSED_STATUS
t . save ( )
2013-01-23 00:59:12 +01:00
f = FollowUp ( ticket = t , date = timezone . now ( ) , title = _ ( ' Closed in bulk update ' ) , public = True , user = request . user , new_status = Ticket . CLOSED_STATUS )
2009-03-08 07:03:55 +01:00
f . save ( )
# Send email to Submitter, Owner, Queue CC
2011-11-19 09:34:07 +01:00
context = safe_template_context ( t )
context . update (
resolution = t . resolution ,
queue = t . queue ,
)
2009-03-08 07:03:55 +01:00
2009-08-11 11:02:48 +02:00
messages_sent_to = [ ]
2009-03-08 07:03:55 +01:00
if t . submitter_email :
send_templated_mail (
' closed_submitter ' ,
context ,
recipients = t . submitter_email ,
sender = t . queue . from_address ,
fail_silently = True ,
)
2009-08-11 11:02:48 +02:00
messages_sent_to . append ( t . submitter_email )
2009-03-08 07:03:55 +01:00
2010-09-04 08:47:55 +02:00
for cc in t . ticketcc_set . all ( ) :
2009-09-09 10:47:48 +02:00
if cc . email_address not in messages_sent_to :
send_templated_mail (
' closed_submitter ' ,
context ,
recipients = cc . email_address ,
2010-09-04 08:47:55 +02:00
sender = t . queue . from_address ,
2009-09-09 10:47:48 +02:00
fail_silently = True ,
)
messages_sent_to . append ( cc . email_address )
2009-08-11 11:02:48 +02:00
if t . assigned_to and request . user != t . assigned_to and t . assigned_to . email and t . assigned_to . email not in messages_sent_to :
2009-03-08 07:03:55 +01:00
send_templated_mail (
' closed_owner ' ,
context ,
recipients = t . assigned_to . email ,
sender = t . queue . from_address ,
fail_silently = True ,
)
2009-08-11 11:02:48 +02:00
messages_sent_to . append ( t . assigned_to . email )
2009-03-08 07:03:55 +01:00
2009-08-11 11:02:48 +02:00
if t . queue . updated_ticket_cc and t . queue . updated_ticket_cc not in messages_sent_to :
2009-03-08 07:03:55 +01:00
send_templated_mail (
' closed_cc ' ,
context ,
recipients = t . queue . updated_ticket_cc ,
sender = t . queue . from_address ,
fail_silently = True ,
)
elif action == ' delete ' :
t . delete ( )
return HttpResponseRedirect ( reverse ( ' helpdesk_list ' ) )
mass_update = staff_member_required ( mass_update )
2007-12-27 01:29:17 +01:00
def ticket_list ( request ) :
context = { }
2009-03-08 07:03:55 +01:00
2012-05-11 17:15:46 +02:00
# Query_params will hold a dictionary of parameters relating to
2008-08-28 11:06:24 +02:00
# a query, to be saved if needed:
query_params = {
' filtering ' : { } ,
' sorting ' : None ,
2008-10-25 00:52:34 +02:00
' sortreverse ' : False ,
2008-08-28 11:06:24 +02:00
' keyword ' : None ,
' other_filter ' : None ,
}
2007-12-27 01:29:17 +01:00
2008-08-29 11:11:02 +02:00
from_saved_query = False
2009-03-08 06:55:35 +01:00
# If the user is coming from the header/navigation search box, lets' first
# look at their query to see if they have entered a valid ticket number. If
# they have, just redirect to that ticket number. Otherwise, we treat it as
# a keyword search.
if request . GET . get ( ' search_type ' , None ) == ' header ' :
query = request . GET . get ( ' q ' )
filter = None
if query . find ( ' - ' ) > 0 :
try :
2012-07-23 08:29:58 +02:00
queue , id = query . split ( ' - ' )
2009-03-08 06:55:35 +01:00
id = int ( id )
except ValueError :
id = None
if id :
filter = { ' queue__slug ' : queue , ' id ' : id }
else :
try :
query = int ( query )
except ValueError :
query = None
if query :
filter = { ' id ' : int ( query ) }
if filter :
try :
ticket = Ticket . objects . get ( * * filter )
return HttpResponseRedirect ( ticket . staff_url )
except Ticket . DoesNotExist :
# Go on to standard keyword searching
pass
2011-05-06 08:55:52 +02:00
saved_query = None
2008-08-28 11:06:24 +02:00
if request . GET . get ( ' saved_query ' , None ) :
from_saved_query = True
try :
saved_query = SavedSearch . objects . get ( pk = request . GET . get ( ' saved_query ' ) )
except SavedSearch . DoesNotExist :
return HttpResponseRedirect ( reverse ( ' helpdesk_list ' ) )
if not ( saved_query . shared or saved_query . user == request . user ) :
return HttpResponseRedirect ( reverse ( ' helpdesk_list ' ) )
2008-11-18 01:14:36 +01:00
import cPickle
from helpdesk . lib import b64decode
query_params = cPickle . loads ( b64decode ( str ( saved_query . query ) ) )
2008-08-29 11:11:02 +02:00
elif not ( request . GET . has_key ( ' queue ' )
or request . GET . has_key ( ' assigned_to ' )
or request . GET . has_key ( ' status ' )
or request . GET . has_key ( ' q ' )
2008-10-25 00:52:34 +02:00
or request . GET . has_key ( ' sort ' )
2014-07-20 10:36:24 +02:00
or request . GET . has_key ( ' sortreverse ' )
2014-01-29 12:00:00 +01:00
) :
2008-08-29 11:11:02 +02:00
2009-03-08 07:03:55 +01:00
# Fall-back if no querying is being done, force the list to only
2008-08-29 11:11:02 +02:00
# show open/reopened/resolved (not closed) cases sorted by creation
# date.
query_params = {
' filtering ' : { ' status__in ' : [ 1 , 2 , 3 ] } ,
' sorting ' : ' created ' ,
}
2008-08-28 11:06:24 +02:00
else :
queues = request . GET . getlist ( ' queue ' )
if queues :
2012-01-17 13:14:21 +01:00
try :
queues = [ int ( q ) for q in queues ]
query_params [ ' filtering ' ] [ ' queue__id__in ' ] = queues
except ValueError :
pass
2008-08-28 11:06:24 +02:00
owners = request . GET . getlist ( ' assigned_to ' )
if owners :
2012-01-17 13:14:21 +01:00
try :
owners = [ int ( u ) for u in owners ]
query_params [ ' filtering ' ] [ ' assigned_to__id__in ' ] = owners
except ValueError :
pass
2008-08-28 11:06:24 +02:00
statuses = request . GET . getlist ( ' status ' )
if statuses :
2012-01-17 13:14:21 +01:00
try :
statuses = [ int ( s ) for s in statuses ]
query_params [ ' filtering ' ] [ ' status__in ' ] = statuses
except ValueError :
pass
2008-08-28 11:06:24 +02:00
2011-05-13 00:02:59 +02:00
date_from = request . GET . get ( ' date_from ' )
if date_from :
query_params [ ' filtering ' ] [ ' created__gte ' ] = date_from
2014-07-20 10:36:24 +02:00
2011-05-13 00:02:59 +02:00
date_to = request . GET . get ( ' date_to ' )
if date_to :
query_params [ ' filtering ' ] [ ' created__lte ' ] = date_to
2008-08-28 11:06:24 +02:00
### KEYWORD SEARCHING
q = request . GET . get ( ' q ' , None )
if q :
qset = (
Q ( title__icontains = q ) |
Q ( description__icontains = q ) |
Q ( resolution__icontains = q ) |
Q ( submitter_email__icontains = q )
)
context = dict ( context , query = q )
2009-03-08 07:03:55 +01:00
2008-08-28 11:06:24 +02:00
query_params [ ' other_filter ' ] = qset
### SORTING
sort = request . GET . get ( ' sort ' , None )
if sort not in ( ' status ' , ' assigned_to ' , ' created ' , ' title ' , ' queue ' , ' priority ' ) :
sort = ' created '
query_params [ ' sorting ' ] = sort
2009-03-08 07:03:55 +01:00
2008-10-25 00:52:34 +02:00
sortreverse = request . GET . get ( ' sortreverse ' , None )
query_params [ ' sortreverse ' ] = sortreverse
2008-08-28 11:06:24 +02:00
2012-01-17 13:14:21 +01:00
try :
ticket_qs = apply_query ( Ticket . objects . select_related ( ) , query_params )
except ValidationError :
# invalid parameters in query, return default query
query_params = {
' filtering ' : { ' status__in ' : [ 1 , 2 , 3 ] } ,
' sorting ' : ' created ' ,
}
ticket_qs = apply_query ( Ticket . objects . select_related ( ) , query_params )
2009-09-09 11:11:05 +02:00
2009-08-22 08:29:30 +02:00
ticket_paginator = paginator . Paginator ( ticket_qs , request . user . usersettings . settings . get ( ' tickets_per_page ' ) or 20 )
2009-07-21 12:29:23 +02:00
try :
page = int ( request . GET . get ( ' page ' , ' 1 ' ) )
except ValueError :
page = 1
try :
2009-08-18 14:12:35 +02:00
tickets = ticket_paginator . page ( page )
except ( paginator . EmptyPage , paginator . InvalidPage ) :
tickets = ticket_paginator . page ( ticket_paginator . num_pages )
2008-08-28 11:06:24 +02:00
2009-03-08 06:49:08 +01:00
search_message = ' '
2012-06-11 05:40:54 +02:00
if context . has_key ( ' query ' ) and settings . DATABASES [ ' default ' ] [ ' ENGINE ' ] . endswith ( ' sqlite ' ) :
2009-03-08 06:49:08 +01:00
search_message = _ ( ' <p><strong>Note:</strong> Your keyword search is case sensitive because of your database. This means the search will <strong>not</strong> be accurate. By switching to a different database system you will gain better searching! For more information, read the <a href= " http://docs.djangoproject.com/en/dev/ref/databases/#sqlite-string-matching " >Django Documentation on string matching in SQLite</a>. ' )
2009-03-08 07:03:55 +01:00
2008-11-18 01:14:36 +01:00
import cPickle
from helpdesk . lib import b64encode
urlsafe_query = b64encode ( cPickle . dumps ( query_params ) )
2008-08-19 10:50:38 +02:00
2008-08-28 11:06:24 +02:00
user_saved_queries = SavedSearch . objects . filter ( Q ( user = request . user ) | Q ( shared__exact = True ) )
2007-12-27 01:29:17 +01:00
2012-05-11 17:15:46 +02:00
querydict = request . GET . copy ( )
querydict . pop ( ' page ' , 1 )
2009-07-21 12:29:23 +02:00
2009-09-09 11:11:05 +02:00
2007-12-27 01:29:17 +01:00
return render_to_response ( ' helpdesk/ticket_list.html ' ,
RequestContext ( request , dict (
context ,
2012-05-11 17:15:46 +02:00
query_string = querydict . urlencode ( ) ,
2007-12-27 01:29:17 +01:00
tickets = tickets ,
2012-07-23 09:08:26 +02:00
user_choices = User . objects . filter ( is_active = True , is_staff = True ) ,
2007-12-27 01:29:17 +01:00
queue_choices = Queue . objects . all ( ) ,
status_choices = Ticket . STATUS_CHOICES ,
2008-08-28 11:06:24 +02:00
urlsafe_query = urlsafe_query ,
user_saved_queries = user_saved_queries ,
query_params = query_params ,
from_saved_query = from_saved_query ,
2011-05-06 08:55:52 +02:00
saved_query = saved_query ,
2009-03-08 06:49:08 +01:00
search_message = search_message ,
2007-12-27 01:29:17 +01:00
) ) )
2009-01-11 01:26:00 +01:00
ticket_list = staff_member_required ( ticket_list )
2007-12-27 01:29:17 +01:00
2008-08-19 10:50:38 +02:00
2009-06-03 13:43:46 +02:00
def edit_ticket ( request , ticket_id ) :
ticket = get_object_or_404 ( Ticket , id = ticket_id )
if request . method == ' POST ' :
form = EditTicketForm ( request . POST , instance = ticket )
if form . is_valid ( ) :
ticket = form . save ( )
return HttpResponseRedirect ( ticket . get_absolute_url ( ) )
else :
form = EditTicketForm ( instance = ticket )
2014-07-20 10:36:24 +02:00
2009-06-03 13:43:46 +02:00
return render_to_response ( ' helpdesk/edit_ticket.html ' ,
RequestContext ( request , {
' form ' : form ,
} ) )
2009-09-09 10:47:48 +02:00
edit_ticket = staff_member_required ( edit_ticket )
2009-06-03 13:43:46 +02:00
2007-12-27 01:29:17 +01:00
def create_ticket ( request ) :
2012-10-07 10:44:32 +02:00
if helpdesk_settings . HELPDESK_STAFF_ONLY_TICKET_OWNERS :
assignable_users = User . objects . filter ( is_active = True , is_staff = True ) . order_by ( ' username ' )
else :
assignable_users = User . objects . filter ( is_active = True ) . order_by ( ' username ' )
2014-07-20 10:36:24 +02:00
2007-12-27 01:29:17 +01:00
if request . method == ' POST ' :
2009-01-22 09:08:22 +01:00
form = TicketForm ( request . POST , request . FILES )
2007-12-27 01:29:17 +01:00
form . fields [ ' queue ' ] . choices = [ ( ' ' , ' -------- ' ) ] + [ [ q . id , q . title ] for q in Queue . objects . all ( ) ]
2012-10-07 10:44:32 +02:00
form . fields [ ' assigned_to ' ] . choices = [ ( ' ' , ' -------- ' ) ] + [ [ u . id , u . username ] for u in assignable_users ]
2007-12-27 01:29:17 +01:00
if form . is_valid ( ) :
2007-12-28 04:29:45 +01:00
ticket = form . save ( user = request . user )
2007-12-27 01:29:17 +01:00
return HttpResponseRedirect ( ticket . get_absolute_url ( ) )
else :
2009-08-06 10:56:02 +02:00
initial_data = { }
if request . user . usersettings . settings . get ( ' use_email_as_submitter ' , False ) and request . user . email :
initial_data [ ' submitter_email ' ] = request . user . email
2011-02-06 19:17:55 +01:00
if request . GET . has_key ( ' queue ' ) :
initial_data [ ' queue ' ] = request . GET [ ' queue ' ]
2009-08-06 10:56:02 +02:00
form = TicketForm ( initial = initial_data )
2007-12-27 01:29:17 +01:00
form . fields [ ' queue ' ] . choices = [ ( ' ' , ' -------- ' ) ] + [ [ q . id , q . title ] for q in Queue . objects . all ( ) ]
2012-10-07 10:44:32 +02:00
form . fields [ ' assigned_to ' ] . choices = [ ( ' ' , ' -------- ' ) ] + [ [ u . id , u . username ] for u in assignable_users ]
2011-11-28 15:17:54 +01:00
if helpdesk_settings . HELPDESK_CREATE_TICKET_HIDE_ASSIGNED_TO :
form . fields [ ' assigned_to ' ] . widget = forms . HiddenInput ( )
2007-12-27 01:29:17 +01:00
2008-08-19 10:50:38 +02:00
return render_to_response ( ' helpdesk/create_ticket.html ' ,
2007-12-27 01:29:17 +01:00
RequestContext ( request , {
' form ' : form ,
} ) )
2009-01-11 01:26:00 +01:00
create_ticket = staff_member_required ( create_ticket )
2008-01-11 02:00:01 +01:00
2008-08-19 10:50:38 +02:00
2008-01-11 02:00:01 +01:00
def raw_details ( request , type ) :
2008-08-19 10:50:38 +02:00
# TODO: This currently only supports spewing out 'PreSetReply' objects,
# in the future it needs to be expanded to include other items. All it
# does is return a plain-text representation of an object.
2008-01-11 02:00:01 +01:00
if not type in ( ' preset ' , ) :
raise Http404
2008-08-19 10:50:38 +02:00
2008-01-11 02:00:01 +01:00
if type == ' preset ' and request . GET . get ( ' id ' , False ) :
try :
preset = PreSetReply . objects . get ( id = request . GET . get ( ' id ' ) )
return HttpResponse ( preset . body )
except PreSetReply . DoesNotExist :
raise Http404
2008-08-19 10:50:38 +02:00
2008-01-11 02:00:01 +01:00
raise Http404
2009-01-11 01:26:00 +01:00
raw_details = staff_member_required ( raw_details )
2008-01-16 01:26:24 +01:00
2008-08-19 10:50:38 +02:00
2008-01-16 05:52:30 +01:00
def hold_ticket ( request , ticket_id , unhold = False ) :
ticket = get_object_or_404 ( Ticket , id = ticket_id )
if unhold :
ticket . on_hold = False
2008-05-07 11:04:18 +02:00
title = _ ( ' Ticket taken off hold ' )
2008-01-16 05:52:30 +01:00
else :
ticket . on_hold = True
2008-05-07 11:04:18 +02:00
title = _ ( ' Ticket placed on hold ' )
2008-08-19 10:50:38 +02:00
2008-01-16 05:52:30 +01:00
f = FollowUp (
ticket = ticket ,
user = request . user ,
title = title ,
2013-01-23 00:59:12 +01:00
date = timezone . now ( ) ,
2008-01-21 00:31:27 +01:00
public = True ,
2008-01-16 05:52:30 +01:00
)
f . save ( )
2008-08-19 10:50:38 +02:00
2008-01-16 05:52:30 +01:00
ticket . save ( )
2008-08-19 10:50:38 +02:00
2008-01-16 05:52:30 +01:00
return HttpResponseRedirect ( ticket . get_absolute_url ( ) )
2009-01-11 01:26:00 +01:00
hold_ticket = staff_member_required ( hold_ticket )
2008-01-16 05:52:30 +01:00
2008-08-19 10:50:38 +02:00
2008-01-16 05:52:30 +01:00
def unhold_ticket ( request , ticket_id ) :
return hold_ticket ( request , ticket_id , unhold = True )
2009-01-11 01:26:00 +01:00
unhold_ticket = staff_member_required ( unhold_ticket )
2008-01-16 05:52:30 +01:00
2008-08-19 10:50:38 +02:00
2008-02-08 06:29:51 +01:00
def rss_list ( request ) :
2008-08-19 10:50:38 +02:00
return render_to_response ( ' helpdesk/rss_list.html ' ,
2008-02-08 06:29:51 +01:00
RequestContext ( request , {
' queues ' : Queue . objects . all ( ) ,
} ) )
2009-01-11 01:26:00 +01:00
rss_list = staff_member_required ( rss_list )
2008-04-02 01:26:12 +02:00
2008-08-19 10:50:38 +02:00
2008-04-02 01:26:12 +02:00
def report_index ( request ) :
2009-12-23 12:47:50 +01:00
number_tickets = Ticket . objects . all ( ) . count ( )
2011-05-12 01:01:35 +02:00
saved_query = request . GET . get ( ' saved_query ' , None )
2008-04-02 01:26:12 +02:00
return render_to_response ( ' helpdesk/report_index.html ' ,
2009-12-23 12:47:50 +01:00
RequestContext ( request , {
' number_tickets ' : number_tickets ,
2011-05-11 12:07:46 +02:00
' saved_query ' : saved_query ,
2009-12-23 12:47:50 +01:00
} ) )
2009-01-11 01:26:00 +01:00
report_index = staff_member_required ( report_index )
2008-04-02 01:26:12 +02:00
2008-08-19 10:50:38 +02:00
2008-04-02 01:26:12 +02:00
def run_report ( request , report ) :
2012-08-07 15:51:52 +02:00
if Ticket . objects . all ( ) . count ( ) == 0 or report not in ( ' queuemonth ' , ' usermonth ' , ' queuestatus ' , ' queuepriority ' , ' userstatus ' , ' userpriority ' , ' userqueue ' , ' daysuntilticketclosedbymonth ' ) :
2011-04-19 23:36:20 +02:00
return HttpResponseRedirect ( reverse ( " helpdesk_report_index " ) )
2011-05-11 12:07:46 +02:00
report_queryset = Ticket . objects . all ( ) . select_related ( )
2014-07-20 10:36:24 +02:00
2011-05-11 12:07:46 +02:00
from_saved_query = False
saved_query = None
if request . GET . get ( ' saved_query ' , None ) :
from_saved_query = True
try :
saved_query = SavedSearch . objects . get ( pk = request . GET . get ( ' saved_query ' ) )
except SavedSearch . DoesNotExist :
return HttpResponseRedirect ( reverse ( ' helpdesk_report_index ' ) )
if not ( saved_query . shared or saved_query . user == request . user ) :
return HttpResponseRedirect ( reverse ( ' helpdesk_report_index ' ) )
import cPickle
from helpdesk . lib import b64decode
query_params = cPickle . loads ( b64decode ( str ( saved_query . query ) ) )
report_queryset = apply_query ( report_queryset , query_params )
from collections import defaultdict
summarytable = defaultdict ( int )
2012-08-07 15:51:52 +02:00
# a second table for more complex queries
summarytable2 = defaultdict ( int )
2014-07-21 09:12:27 +02:00
month_name = lambda m : MONTHS_3 [ m ] . title ( )
2014-07-20 10:36:24 +02:00
2008-04-02 01:26:12 +02:00
first_ticket = Ticket . objects . all ( ) . order_by ( ' created ' ) [ 0 ]
first_month = first_ticket . created . month
first_year = first_ticket . created . year
2008-08-19 10:50:38 +02:00
2008-04-02 01:26:12 +02:00
last_ticket = Ticket . objects . all ( ) . order_by ( ' -created ' ) [ 0 ]
last_month = last_ticket . created . month
last_year = last_ticket . created . year
periods = [ ]
year , month = first_year , first_month
working = True
2014-07-21 09:12:27 +02:00
periods . append ( " %s %s " % ( month_name ( month ) , year ) )
2008-04-02 01:26:12 +02:00
while working :
month + = 1
if month > 12 :
year + = 1
month = 1
2009-12-16 09:30:50 +01:00
if ( year > last_year ) or ( month > last_month and year > = last_year ) :
2008-04-02 01:26:12 +02:00
working = False
2014-07-21 09:12:27 +02:00
periods . append ( " %s %s " % ( month_name ( month ) , year ) )
2008-04-02 01:26:12 +02:00
if report == ' userpriority ' :
2011-05-11 12:07:46 +02:00
title = _ ( ' User by Priority ' )
col1heading = _ ( ' User ' )
possible_options = [ t [ 1 ] . __unicode__ ( ) for t in Ticket . PRIORITY_CHOICES ]
charttype = ' bar '
2008-08-19 10:50:38 +02:00
2008-04-02 01:26:12 +02:00
elif report == ' userqueue ' :
2011-05-11 12:07:46 +02:00
title = _ ( ' User by Queue ' )
col1heading = _ ( ' User ' )
possible_options = [ q . title . encode ( ' utf-8 ' ) for q in Queue . objects . all ( ) ]
charttype = ' bar '
2008-08-19 10:50:38 +02:00
2008-04-02 01:26:12 +02:00
elif report == ' userstatus ' :
2011-05-11 12:07:46 +02:00
title = _ ( ' User by Status ' )
col1heading = _ ( ' User ' )
possible_options = [ s [ 1 ] . __unicode__ ( ) for s in Ticket . STATUS_CHOICES ]
charttype = ' bar '
2008-08-19 10:50:38 +02:00
2008-04-02 01:26:12 +02:00
elif report == ' usermonth ' :
2011-05-11 12:07:46 +02:00
title = _ ( ' User by Month ' )
col1heading = _ ( ' User ' )
possible_options = periods
charttype = ' date '
2008-08-19 10:50:38 +02:00
2008-04-02 01:26:12 +02:00
elif report == ' queuepriority ' :
2011-05-11 12:07:46 +02:00
title = _ ( ' Queue by Priority ' )
col1heading = _ ( ' Queue ' )
possible_options = [ t [ 1 ] . __unicode__ ( ) for t in Ticket . PRIORITY_CHOICES ]
charttype = ' bar '
2008-08-19 10:50:38 +02:00
2008-04-02 01:26:12 +02:00
elif report == ' queuestatus ' :
2011-05-11 12:07:46 +02:00
title = _ ( ' Queue by Status ' )
col1heading = _ ( ' Queue ' )
possible_options = [ s [ 1 ] . __unicode__ ( ) for s in Ticket . STATUS_CHOICES ]
charttype = ' bar '
2008-08-19 10:50:38 +02:00
2008-04-02 01:26:12 +02:00
elif report == ' queuemonth ' :
2011-05-11 12:07:46 +02:00
title = _ ( ' Queue by Month ' )
col1heading = _ ( ' Queue ' )
possible_options = periods
charttype = ' date '
2008-04-02 01:26:12 +02:00
2012-08-07 15:51:52 +02:00
elif report == ' daysuntilticketclosedbymonth ' :
title = _ ( ' Days until ticket closed by Month ' )
col1heading = _ ( ' Queue ' )
possible_options = periods
charttype = ' date '
2008-04-02 01:26:12 +02:00
2012-08-07 15:51:52 +02:00
metric3 = False
2011-05-11 12:07:46 +02:00
for ticket in report_queryset :
if report == ' userpriority ' :
metric1 = u ' %s ' % ticket . get_assigned_to
metric2 = u ' %s ' % ticket . get_priority_display ( )
2008-04-02 01:26:12 +02:00
2011-05-11 12:07:46 +02:00
elif report == ' userqueue ' :
metric1 = u ' %s ' % ticket . get_assigned_to
metric2 = u ' %s ' % ticket . queue . title
2008-04-02 01:26:12 +02:00
2011-05-11 12:07:46 +02:00
elif report == ' userstatus ' :
metric1 = u ' %s ' % ticket . get_assigned_to
metric2 = u ' %s ' % ticket . get_status_display ( )
elif report == ' usermonth ' :
metric1 = u ' %s ' % ticket . get_assigned_to
2014-07-21 09:12:27 +02:00
metric2 = u ' %s %s ' % ( month_name ( ticket . created . month ) , ticket . created . year )
2011-05-11 12:07:46 +02:00
elif report == ' queuepriority ' :
metric1 = u ' %s ' % ticket . queue . title
metric2 = u ' %s ' % ticket . get_priority_display ( )
elif report == ' queuestatus ' :
metric1 = u ' %s ' % ticket . queue . title
metric2 = u ' %s ' % ticket . get_status_display ( )
elif report == ' queuemonth ' :
metric1 = u ' %s ' % ticket . queue . title
2014-07-21 09:12:27 +02:00
metric2 = u ' %s %s ' % ( month_name ( ticket . created . month ) , ticket . created . year )
2011-05-11 12:07:46 +02:00
2012-08-07 15:51:52 +02:00
elif report == ' daysuntilticketclosedbymonth ' :
metric1 = u ' %s ' % ticket . queue . title
2014-07-21 09:12:27 +02:00
metric2 = u ' %s %s ' % ( month_name ( ticket . created . month ) , ticket . created . year )
2012-08-07 15:51:52 +02:00
metric3 = ticket . modified - ticket . created
metric3 = metric3 . days
2011-05-11 12:07:46 +02:00
summarytable [ metric1 , metric2 ] + = 1
2012-08-07 15:51:52 +02:00
if metric3 :
if report == ' daysuntilticketclosedbymonth ' :
summarytable2 [ metric1 , metric2 ] + = metric3
2014-07-20 10:36:24 +02:00
2011-05-11 12:07:46 +02:00
table = [ ]
2014-07-20 10:36:24 +02:00
2012-08-07 15:51:52 +02:00
if report == ' daysuntilticketclosedbymonth ' :
for key in summarytable2 . keys ( ) :
summarytable [ key ] = summarytable2 [ key ] / summarytable [ key ]
2011-05-11 12:07:46 +02:00
header1 = sorted ( set ( list ( i . encode ( ' utf-8 ' ) for i , _ in summarytable . keys ( ) ) ) )
2014-07-20 10:36:24 +02:00
2011-05-11 12:07:46 +02:00
column_headings = [ col1heading ] + possible_options
# Pivot the data so that 'header1' fields are always first column
# in the row, and 'possible_options' are always the 2nd - nth columns.
for item in header1 :
data = [ ]
for hdr in possible_options :
data . append ( summarytable [ item , hdr ] )
table . append ( [ item ] + data )
2008-08-19 10:50:38 +02:00
2008-04-02 01:26:12 +02:00
return render_to_response ( ' helpdesk/report_output.html ' ,
RequestContext ( request , {
2008-10-25 00:52:34 +02:00
' title ' : title ,
2011-05-03 00:32:51 +02:00
' charttype ' : charttype ,
2011-05-11 12:07:46 +02:00
' data ' : table ,
' headings ' : column_headings ,
' from_saved_query ' : from_saved_query ,
' saved_query ' : saved_query ,
2008-04-02 01:26:12 +02:00
} ) )
2009-01-11 01:26:00 +01:00
run_report = staff_member_required ( run_report )
2008-08-19 10:50:38 +02:00
2008-08-28 11:06:24 +02:00
def save_query ( request ) :
title = request . POST . get ( ' title ' , None )
shared = request . POST . get ( ' shared ' , False )
query_encoded = request . POST . get ( ' query_encoded ' , None )
2009-03-08 07:03:55 +01:00
2008-08-28 11:06:24 +02:00
if not title or not query_encoded :
return HttpResponseRedirect ( reverse ( ' helpdesk_list ' ) )
2009-03-08 07:03:55 +01:00
2008-08-28 11:06:24 +02:00
query = SavedSearch ( title = title , shared = shared , query = query_encoded , user = request . user )
query . save ( )
2009-03-08 07:03:55 +01:00
2008-08-28 11:06:24 +02:00
return HttpResponseRedirect ( ' %s ?saved_query= %s ' % ( reverse ( ' helpdesk_list ' ) , query . id ) )
2009-01-11 01:26:00 +01:00
save_query = staff_member_required ( save_query )
2008-08-28 11:06:24 +02:00
def delete_saved_query ( request , id ) :
query = get_object_or_404 ( SavedSearch , id = id , user = request . user )
if request . method == ' POST ' :
query . delete ( )
return HttpResponseRedirect ( reverse ( ' helpdesk_list ' ) )
else :
return render_to_response ( ' helpdesk/confirm_delete_saved_query.html ' ,
RequestContext ( request , {
' query ' : query ,
} ) )
2009-01-11 01:26:00 +01:00
delete_saved_query = staff_member_required ( delete_saved_query )
2008-09-09 10:32:01 +02:00
2008-10-25 00:52:34 +02:00
2008-09-09 10:32:01 +02:00
def user_settings ( request ) :
s = request . user . usersettings
if request . POST :
form = UserSettingsForm ( request . POST )
if form . is_valid ( ) :
s . settings = form . cleaned_data
s . save ( )
else :
form = UserSettingsForm ( s . settings )
return render_to_response ( ' helpdesk/user_settings.html ' ,
RequestContext ( request , {
' form ' : form ,
} ) )
2009-01-11 01:26:00 +01:00
user_settings = staff_member_required ( user_settings )
2008-10-25 00:52:34 +02:00
def email_ignore ( request ) :
return render_to_response ( ' helpdesk/email_ignore_list.html ' ,
RequestContext ( request , {
' ignore_list ' : IgnoreEmail . objects . all ( ) ,
} ) )
email_ignore = superuser_required ( email_ignore )
def email_ignore_add ( request ) :
if request . method == ' POST ' :
form = EmailIgnoreForm ( request . POST )
if form . is_valid ( ) :
ignore = form . save ( )
return HttpResponseRedirect ( reverse ( ' helpdesk_email_ignore ' ) )
else :
form = EmailIgnoreForm ( request . GET )
return render_to_response ( ' helpdesk/email_ignore_add.html ' ,
RequestContext ( request , {
' form ' : form ,
} ) )
email_ignore_add = superuser_required ( email_ignore_add )
def email_ignore_del ( request , id ) :
ignore = get_object_or_404 ( IgnoreEmail , id = id )
if request . method == ' POST ' :
ignore . delete ( )
return HttpResponseRedirect ( reverse ( ' helpdesk_email_ignore ' ) )
else :
return render_to_response ( ' helpdesk/email_ignore_del.html ' ,
RequestContext ( request , {
' ignore ' : ignore ,
} ) )
email_ignore_del = superuser_required ( email_ignore_del )
2009-09-09 10:47:48 +02:00
def ticket_cc ( request , ticket_id ) :
ticket = get_object_or_404 ( Ticket , id = ticket_id )
copies_to = ticket . ticketcc_set . all ( )
return render_to_response ( ' helpdesk/ticket_cc_list.html ' ,
RequestContext ( request , {
' copies_to ' : copies_to ,
' ticket ' : ticket ,
} ) )
ticket_cc = staff_member_required ( ticket_cc )
def ticket_cc_add ( request , ticket_id ) :
ticket = get_object_or_404 ( Ticket , id = ticket_id )
if request . method == ' POST ' :
form = TicketCCForm ( request . POST )
if form . is_valid ( ) :
ticketcc = form . save ( commit = False )
ticketcc . ticket = ticket
ticketcc . save ( )
return HttpResponseRedirect ( reverse ( ' helpdesk_ticket_cc ' , kwargs = { ' ticket_id ' : ticket . id } ) )
else :
form = TicketCCForm ( )
return render_to_response ( ' helpdesk/ticket_cc_add.html ' ,
RequestContext ( request , {
' ticket ' : ticket ,
' form ' : form ,
} ) )
ticket_cc_add = staff_member_required ( ticket_cc_add )
def ticket_cc_del ( request , ticket_id , cc_id ) :
cc = get_object_or_404 ( TicketCC , ticket__id = ticket_id , id = cc_id )
2010-01-26 01:01:22 +01:00
if request . method == ' POST ' :
2009-09-09 10:47:48 +02:00
cc . delete ( )
return HttpResponseRedirect ( reverse ( ' helpdesk_ticket_cc ' , kwargs = { ' ticket_id ' : cc . ticket . id } ) )
return render_to_response ( ' helpdesk/ticket_cc_del.html ' ,
RequestContext ( request , {
' cc ' : cc ,
} ) )
ticket_cc_del = staff_member_required ( ticket_cc_del )
2011-05-10 11:27:11 +02:00
def ticket_dependency_add ( request , ticket_id ) :
ticket = get_object_or_404 ( Ticket , id = ticket_id )
if request . method == ' POST ' :
form = TicketDependencyForm ( request . POST )
if form . is_valid ( ) :
ticketdependency = form . save ( commit = False )
ticketdependency . ticket = ticket
if ticketdependency . ticket < > ticketdependency . depends_on :
ticketdependency . save ( )
return HttpResponseRedirect ( reverse ( ' helpdesk_view ' , args = [ ticket . id ] ) )
else :
form = TicketDependencyForm ( )
return render_to_response ( ' helpdesk/ticket_dependency_add.html ' ,
RequestContext ( request , {
' ticket ' : ticket ,
' form ' : form ,
} ) )
ticket_dependency_add = staff_member_required ( ticket_dependency_add )
def ticket_dependency_del ( request , ticket_id , dependency_id ) :
dependency = get_object_or_404 ( TicketDependency , ticket__id = ticket_id , id = dependency_id )
if request . method == ' POST ' :
dependency . delete ( )
return HttpResponseRedirect ( reverse ( ' helpdesk_view ' , args = [ ticket_id ] ) )
return render_to_response ( ' helpdesk/ticket_dependency_del.html ' ,
RequestContext ( request , {
' dependency ' : dependency ,
} ) )
ticket_dependency_del = staff_member_required ( ticket_dependency_del )
2011-11-19 09:34:07 +01:00
def attachment_del ( request , ticket_id , attachment_id ) :
ticket = get_object_or_404 ( Ticket , id = ticket_id )
attachment = get_object_or_404 ( Attachment , id = attachment_id )
attachment . delete ( )
return HttpResponseRedirect ( reverse ( ' helpdesk_view ' , args = [ ticket_id ] ) )
attachment_del = staff_member_required ( attachment_del )
2012-08-07 15:51:52 +02:00
def calc_average_nbr_days_until_ticket_resolved ( Tickets ) :
nbr_closed_tickets = len ( Tickets )
days_per_ticket = 0
days_each_ticket = list ( )
for ticket in Tickets :
time_ticket_open = ticket . modified - ticket . created
days_this_ticket = time_ticket_open . days
days_per_ticket + = days_this_ticket
days_each_ticket . append ( days_this_ticket )
2012-08-07 15:54:46 +02:00
if nbr_closed_tickets > 0 :
mean_per_ticket = days_per_ticket / nbr_closed_tickets
else :
mean_per_ticket = 0
2012-08-07 15:51:52 +02:00
return mean_per_ticket
def calc_basic_ticket_stats ( Ticket ) :
# all not closed tickets (open, reopened, resolved,) - independent of user
all_open_tickets = Ticket . objects . exclude ( status = Ticket . CLOSED_STATUS )
today = datetime . today ( )
date_30 = date_rel_to_today ( today , 30 )
date_60 = date_rel_to_today ( today , 60 )
date_30_str = date_30 . strftime ( ' % Y- % m- %d ' )
date_60_str = date_60 . strftime ( ' % Y- % m- %d ' )
2014-07-20 10:36:24 +02:00
# > 0 & <= 30
2012-08-07 15:54:46 +02:00
ota_le_30 = all_open_tickets . filter ( created__gte = date_30_str )
2012-08-07 15:51:52 +02:00
N_ota_le_30 = len ( ota_le_30 )
2014-07-20 10:36:24 +02:00
# >= 30 & <= 60
2012-08-07 15:54:46 +02:00
ota_le_60_ge_30 = all_open_tickets . filter ( created__gte = date_60_str , created__lte = date_30_str )
2012-08-07 15:51:52 +02:00
N_ota_le_60_ge_30 = len ( ota_le_60_ge_30 )
2012-08-07 15:54:46 +02:00
# >= 60
ota_ge_60 = all_open_tickets . filter ( created__lte = date_60_str )
2012-08-07 15:51:52 +02:00
N_ota_ge_60 = len ( ota_ge_60 )
# (O)pen (T)icket (S)tats
ots = list ( )
# label, number entries, color, sort_string
ots . append ( [ ' < 30 days ' , N_ota_le_30 , get_color_for_nbr_days ( N_ota_le_30 ) , sort_string ( date_30_str , ' ' ) , ] )
ots . append ( [ ' 30 - 60 days ' , N_ota_le_60_ge_30 , get_color_for_nbr_days ( N_ota_le_60_ge_30 ) , sort_string ( date_60_str , date_30_str ) , ] )
ots . append ( [ ' > 60 days ' , N_ota_ge_60 , get_color_for_nbr_days ( N_ota_ge_60 ) , sort_string ( ' ' , date_60_str ) , ] )
2012-08-07 15:57:34 +02:00
# all closed tickets - independent of user.
all_closed_tickets = Ticket . objects . filter ( status = Ticket . CLOSED_STATUS )
average_nbr_days_until_ticket_closed = calc_average_nbr_days_until_ticket_resolved ( all_closed_tickets )
# all closed tickets that were opened in the last 60 days.
all_closed_last_60_days = all_closed_tickets . filter ( created__gte = date_60_str )
average_nbr_days_until_ticket_closed_last_60_days = calc_average_nbr_days_until_ticket_resolved ( all_closed_last_60_days )
2012-08-07 15:51:52 +02:00
# put together basic stats
2014-07-20 10:36:24 +02:00
basic_ticket_stats = { ' average_nbr_days_until_ticket_closed ' : average_nbr_days_until_ticket_closed ,
2012-08-07 15:57:34 +02:00
' average_nbr_days_until_ticket_closed_last_60_days ' : average_nbr_days_until_ticket_closed_last_60_days ,
2012-08-07 15:51:52 +02:00
' open_ticket_stats ' : ots , }
return basic_ticket_stats
def get_color_for_nbr_days ( nbr_days ) :
''' '''
if nbr_days < 5 :
color_string = ' green '
elif nbr_days > = 5 and nbr_days < 10 :
color_string = ' orange '
else : # more than 10 days
color_string = ' red '
return color_string
def days_since_created ( today , ticket ) :
return ( today - ticket . created ) . days
def date_rel_to_today ( today , offset ) :
return today - timedelta ( days = offset )
def sort_string ( begin , end ) :
return ' sort=created&date_from= %s &date_to= %s &status= %s &status= %s &status= %s ' % ( begin , end , Ticket . OPEN_STATUS , Ticket . REOPENED_STATUS , Ticket . RESOLVED_STATUS )