2008-02-06 05:36:07 +01:00
"""
Jutda 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
2007-12-27 01:29:17 +01:00
from datetime import datetime
2009-01-20 09:12:05 +01:00
from django . conf import settings
2007-12-27 01:29:17 +01:00
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
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
2008-05-07 11:04:18 +02:00
from django . utils . translation import ugettext as _
2007-12-27 01:29:17 +01:00
2009-09-09 10:47:48 +02:00
from helpdesk . forms import TicketForm , UserSettingsForm , EmailIgnoreForm , EditTicketForm , TicketCCForm
2008-08-29 11:11:02 +02:00
from helpdesk . lib import send_templated_mail , line_chart , bar_chart , query_to_dict , apply_query , safe_template_context
2009-09-09 10:47:48 +02:00
from helpdesk . models import Ticket , Queue , FollowUp , TicketChange , PreSetReply , Attachment , SavedSearch , IgnoreEmail , TicketCC
2009-09-09 11:11:05 +02:00
from helpdesk . settings import HAS_TAG_SUPPORT
if HAS_TAG_SUPPORT :
from tagging . models import Tag , TaggedItem
2008-10-25 00:52:34 +02:00
staff_member_required = user_passes_test ( lambda u : u . is_authenticated ( ) and u . is_active and u . is_staff )
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
"""
2008-08-20 03:57:41 +02:00
tickets = Ticket . objects . filter (
assigned_to = request . user ,
) . exclude (
status = Ticket . CLOSED_STATUS ,
)
2008-08-19 10:50:38 +02:00
2008-08-20 03:57:41 +02:00
unassigned_tickets = Ticket . objects . filter (
assigned_to__isnull = True ,
) . exclude (
status = Ticket . CLOSED_STATUS ,
)
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 ( )
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
FROM helpdesk_ticket t ,
helpdesk_queue q
WHERE q . id = t . queue_id
GROUP BY queue , name
ORDER BY q . id ;
""" )
dash_tickets = query_to_dict ( cursor . fetchall ( ) , cursor . description )
return render_to_response ( ' helpdesk/dashboard.html ' ,
RequestContext ( request , {
' user_tickets ' : tickets ,
' unassigned_tickets ' : unassigned_tickets ,
' dash_tickets ' : dash_tickets ,
} ) )
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
2008-08-19 10:50:38 +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.
2007-12-27 01:29:17 +01:00
ticket . assigned_to = request . user
ticket . save ( )
2008-01-15 01:20:00 +01:00
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
return render_to_response ( ' helpdesk/ticket.html ' ,
RequestContext ( request , {
' ticket ' : ticket ,
2009-01-19 10:31:24 +01:00
' active_users ' : User . objects . filter ( is_active = True ) . filter ( is_staff = True ) ,
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 ) ) ,
2009-09-09 11:11:05 +02:00
' tags_enabled ' : HAS_TAG_SUPPORT
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
2008-08-19 10:50:38 +02:00
2009-07-22 10:19:46 +02:00
def update_ticket ( request , ticket_id , public = False ) :
if not ( public or ( request . user . is_authenticated ( ) and request . user . is_active and request . user . is_staff ) ) :
return HttpResponseForbidden ( _ ( ' Sorry, you need to login to do that. ' ) )
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 )
owner = int ( request . POST . get ( ' owner ' , None ) )
2008-01-10 01:28:45 +01:00
priority = int ( request . POST . get ( ' priority ' , ticket . priority ) )
2009-09-09 11:11:05 +02:00
tags = request . POST . get ( ' tags ' , ' ' )
2008-08-19 10:50:38 +02:00
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 )
comment = loader . get_template_from_string ( comment ) . render ( Context ( context ) )
2008-08-29 11:11:02 +02:00
2010-01-26 00:59:16 +01:00
if owner is None and ticket . assigned_to :
2007-12-27 01:29:17 +01:00
owner = ticket . assigned_to . id
2009-01-23 11:35:14 +01:00
f = FollowUp ( ticket = ticket , date = datetime . now ( ) , comment = comment )
2009-03-08 07:03:55 +01:00
2009-01-27 11:19:06 +01:00
if request . user . is_staff :
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
2010-01-26 00:59:16 +01:00
if owner is not None :
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
2010-01-31 03:13:44 +01:00
elif owner == 0 and ticket . assigned_to is not None :
2008-05-07 11:04:18 +02:00
f . title = _ ( ' Unassigned ' )
2007-12-27 01:29:17 +01:00
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 ' ) :
filename = file . name . replace ( ' ' , ' _ ' )
a = Attachment (
followup = f ,
filename = filename ,
mime_type = mimetypes . guess_type ( filename ) [ 0 ] or ' application/octet-stream ' ,
size = file . size ,
)
a . file . save ( file . name , file , save = False )
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
2009-09-09 11:11:05 +02:00
if HAS_TAG_SUPPORT :
if tags != ticket . tags :
c = TicketChange (
followup = f ,
field = _ ( ' Tags ' ) ,
old_value = ticket . tags ,
new_value = tags ,
)
c . save ( )
ticket . tags = tags
2008-01-07 21:22:13 +01:00
if f . new_status == Ticket . RESOLVED_STATUS :
ticket . resolution = comment
2008-08-19 10:50:38 +02:00
2009-08-11 11:02:48 +02:00
messages_sent_to = [ ]
2009-08-18 14:12:35 +02:00
context . update (
resolution = ticket . resolution ,
comment = f . comment ,
)
2008-10-25 00:52:34 +02:00
if ticket . submitter_email and public and ( f . comment or ( f . new_status in ( Ticket . RESOLVED_STATUS , Ticket . CLOSED_STATUS ) ) ) :
2008-08-19 10:50:38 +02:00
2008-01-07 21:22:13 +01:00
if f . new_status == Ticket . RESOLVED_STATUS :
2008-04-02 01:26:12 +02:00
template = ' resolved_submitter '
2008-01-21 02:02:12 +01:00
elif f . new_status == Ticket . CLOSED_STATUS :
2008-04-02 01:26:12 +02:00
template = ' closed_submitter '
2008-01-07 21:22:13 +01:00
else :
2008-04-02 01:26:12 +02:00
template = ' updated_submitter '
2008-08-19 10:50:38 +02:00
send_templated_mail (
template ,
context ,
recipients = ticket . submitter_email ,
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
)
2009-08-11 11:02:48 +02:00
messages_sent_to . append ( ticket . submitter_email )
2008-01-07 21:22:13 +01:00
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 (
template ,
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
2009-01-27 11:19:06 +01:00
if request . user . is_staff :
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 ( )
f = FollowUp ( ticket = t , date = datetime . now ( ) , title = _ ( ' Assigned to %(username)s in bulk update ' % { ' username ' : user . username } ) , public = True , user = request . user )
f . save ( )
elif action == ' unassign ' and t . assigned_to is not None :
t . assigned_to = None
t . save ( )
f = FollowUp ( ticket = t , date = datetime . now ( ) , title = _ ( ' Unassigned in bulk update ' ) , public = True , user = request . user )
f . save ( )
elif action == ' close ' and t . status != Ticket . CLOSED_STATUS :
t . status = Ticket . CLOSED_STATUS
t . save ( )
f = FollowUp ( ticket = t , date = datetime . now ( ) , title = _ ( ' Closed in bulk update ' ) , public = False , user = request . user , new_status = Ticket . CLOSED_STATUS )
f . save ( )
elif action == ' close_public ' and t . status != Ticket . CLOSED_STATUS :
t . status = Ticket . CLOSED_STATUS
t . save ( )
f = FollowUp ( ticket = t , date = datetime . now ( ) , title = _ ( ' Closed in bulk update ' ) , public = True , user = request . user , new_status = Ticket . CLOSED_STATUS )
f . save ( )
# Send email to Submitter, Owner, Queue CC
context = {
' ticket ' : t ,
' queue ' : t . queue ,
' resolution ' : t . resolution ,
}
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
# Query_params will hold a dictionary of paramaters 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 :
queue , id = query . split ( ' - ' )
try :
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
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 ' )
2009-09-09 11:11:05 +02:00
or request . GET . has_key ( ' sortreverse ' )
or request . GET . has_key ( ' tags ' ) ) :
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 :
queues = [ int ( q ) for q in queues ]
query_params [ ' filtering ' ] [ ' queue__id__in ' ] = queues
owners = request . GET . getlist ( ' assigned_to ' )
if owners :
owners = [ int ( u ) for u in owners ]
query_params [ ' filtering ' ] [ ' assigned_to__id__in ' ] = owners
statuses = request . GET . getlist ( ' status ' )
if statuses :
statuses = [ int ( s ) for s in statuses ]
query_params [ ' filtering ' ] [ ' status__in ' ] = statuses
### 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
2009-07-21 12:29:23 +02:00
ticket_qs = apply_query ( Ticket . objects . select_related ( ) , query_params )
2009-09-09 11:11:05 +02:00
## TAG MATCHING
if HAS_TAG_SUPPORT :
tags = request . GET . getlist ( ' tags ' )
if tags :
ticket_qs = TaggedItem . objects . get_by_model ( ticket_qs , tags )
query_params [ ' tags ' ] = tags
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 = ' '
if context . has_key ( ' query ' ) and settings . DATABASE_ENGINE . startswith ( ' sqlite ' ) :
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
2009-07-21 12:29:23 +02:00
query_string = [ ]
for get_key , get_value in request . GET . iteritems ( ) :
if get_key != " page " :
query_string . append ( " %s = %s " % ( get_key , get_value ) )
2009-09-09 11:11:05 +02:00
tag_choices = [ ]
if HAS_TAG_SUPPORT :
# FIXME: restrict this to tags that are actually in use
tag_choices = Tag . objects . all ( )
2007-12-27 01:29:17 +01:00
return render_to_response ( ' helpdesk/ticket_list.html ' ,
RequestContext ( request , dict (
context ,
2009-07-21 12:29:23 +02:00
query_string = " & " . join ( query_string ) ,
2007-12-27 01:29:17 +01:00
tickets = tickets ,
user_choices = User . objects . filter ( is_active = True ) ,
queue_choices = Queue . objects . all ( ) ,
status_choices = Ticket . STATUS_CHOICES ,
2009-09-09 11:11:05 +02:00
tag_choices = tag_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 ,
2009-03-08 06:49:08 +01:00
search_message = search_message ,
2009-09-09 11:11:05 +02:00
tags_enabled = HAS_TAG_SUPPORT
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 )
return render_to_response ( ' helpdesk/edit_ticket.html ' ,
RequestContext ( request , {
' form ' : form ,
2009-09-09 11:11:05 +02:00
' tags_enabled ' : HAS_TAG_SUPPORT ,
2009-06-03 13:43:46 +02:00
} ) )
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 ) :
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 ( ) ]
form . fields [ ' assigned_to ' ] . choices = [ ( ' ' , ' -------- ' ) ] + [ [ u . id , u . username ] for u in User . objects . filter ( is_active = True ) ]
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
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 ( ) ]
form . fields [ ' assigned_to ' ] . choices = [ ( ' ' , ' -------- ' ) ] + [ [ u . id , u . username ] for u in User . objects . filter ( is_active = True ) ]
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-09-09 11:11:05 +02:00
' tags_enabled ' : HAS_TAG_SUPPORT ,
2007-12-27 01:29:17 +01:00
} ) )
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 ,
date = datetime . 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 ( )
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 ,
} ) )
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 ) :
priority_sql = [ ]
priority_columns = [ ]
for p in Ticket . PRIORITY_CHOICES :
2008-05-07 11:04:18 +02:00
priority_sql . append ( " COUNT(CASE t.priority WHEN ' %s ' THEN t.id END) AS \" %s \" " % ( p [ 0 ] , p [ 1 ] . _proxy____unicode_cast ( ) ) )
priority_columns . append ( " %s " % p [ 1 ] . _proxy____unicode_cast ( ) )
2008-04-02 01:26:12 +02:00
priority_sql = " , " . join ( priority_sql )
2008-08-19 10:50:38 +02:00
2008-04-02 01:26:12 +02:00
status_sql = [ ]
status_columns = [ ]
for s in Ticket . STATUS_CHOICES :
2008-05-07 11:04:18 +02:00
status_sql . append ( " COUNT(CASE t.status WHEN ' %s ' THEN t.id END) AS \" %s \" " % ( s [ 0 ] , s [ 1 ] . _proxy____unicode_cast ( ) ) )
status_columns . append ( " %s " % s [ 1 ] . _proxy____unicode_cast ( ) )
2008-04-02 01:26:12 +02:00
status_sql = " , " . join ( status_sql )
2008-08-19 10:50:38 +02:00
2008-04-02 01:26:12 +02:00
queue_sql = [ ]
queue_columns = [ ]
for q in Queue . objects . all ( ) :
queue_sql . append ( " COUNT(CASE t.queue_id WHEN ' %s ' THEN t.id END) AS \" %s \" " % ( q . id , q . title ) )
queue_columns . append ( q . title )
queue_sql = " , " . join ( queue_sql )
month_sql = [ ]
months = (
' Jan ' ,
' Feb ' ,
' Mar ' ,
' Apr ' ,
' May ' ,
' Jun ' ,
' Jul ' ,
' Aug ' ,
' Sep ' ,
' Oct ' ,
' Nov ' ,
' Dec ' ,
)
month_columns = [ ]
2008-08-19 10:50:38 +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
while working :
2008-08-20 12:43:46 +02:00
temp = ( year , month )
2008-04-02 01:26:12 +02:00
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
2008-08-20 12:43:46 +02:00
periods . append ( ( temp , ( year , month ) ) )
for ( low_bound , upper_bound ) in periods :
low_sqlmonth = ' %s - %02i -01 ' % ( low_bound [ 0 ] , low_bound [ 1 ] )
upper_sqlmonth = ' %s - %02i -01 ' % ( upper_bound [ 0 ] , upper_bound [ 1 ] )
desc = ' %s %s ' % ( months [ low_bound [ 1 ] - 1 ] , low_bound [ 0 ] )
month_sql . append ( """
COUNT (
2009-03-08 07:03:55 +01:00
CASE 1 = 1
WHEN ( date ( t . created ) > = date ( ' %s ' )
2008-08-20 12:43:46 +02:00
AND date ( t . created ) < date ( ' %s ' ) ) THEN t . id END ) AS " %s "
""" % (low_sqlmonth, upper_sqlmonth, desc))
2008-04-02 01:26:12 +02:00
month_columns . append ( desc )
2008-08-20 12:43:46 +02:00
2008-04-02 01:26:12 +02:00
month_sql = " , " . join ( month_sql )
queue_base_sql = """
SELECT q . title as queue , % s
FROM helpdesk_ticket t ,
helpdesk_queue q
WHERE q . id = t . queue_id
GROUP BY queue
ORDER BY queue ;
"""
user_base_sql = """
SELECT u . username as username , % s
FROM helpdesk_ticket t ,
auth_user u
WHERE u . id = t . assigned_to_id
GROUP BY u . username
ORDER BY u . username ;
"""
if report == ' userpriority ' :
sql = user_base_sql % priority_sql
columns = [ ' username ' ] + priority_columns
2008-10-25 00:52:34 +02:00
title = ' User by Priority '
2008-08-19 10:50:38 +02:00
2008-04-02 01:26:12 +02:00
elif report == ' userqueue ' :
sql = user_base_sql % queue_sql
columns = [ ' username ' ] + queue_columns
2008-10-25 00:52:34 +02:00
title = ' User by Queue '
2008-08-19 10:50:38 +02:00
2008-04-02 01:26:12 +02:00
elif report == ' userstatus ' :
sql = user_base_sql % status_sql
columns = [ ' username ' ] + status_columns
2008-10-25 00:52:34 +02:00
title = ' User by Status '
2008-08-19 10:50:38 +02:00
2008-04-02 01:26:12 +02:00
elif report == ' usermonth ' :
sql = user_base_sql % month_sql
columns = [ ' username ' ] + month_columns
2008-10-25 00:52:34 +02:00
title = ' User by Month '
2008-08-19 10:50:38 +02:00
2008-04-02 01:26:12 +02:00
elif report == ' queuepriority ' :
sql = queue_base_sql % priority_sql
columns = [ ' queue ' ] + priority_columns
2008-10-25 00:52:34 +02:00
title = ' Queue by Priority '
2008-08-19 10:50:38 +02:00
2008-04-02 01:26:12 +02:00
elif report == ' queuestatus ' :
sql = queue_base_sql % status_sql
columns = [ ' queue ' ] + status_columns
2008-10-25 00:52:34 +02:00
title = ' Queue by Status '
2008-08-19 10:50:38 +02:00
2008-04-02 01:26:12 +02:00
elif report == ' queuemonth ' :
sql = queue_base_sql % month_sql
columns = [ ' queue ' ] + month_columns
2008-10-25 00:52:34 +02:00
title = ' Queue by Month '
2008-04-02 01:26:12 +02:00
cursor = connection . cursor ( )
cursor . execute ( sql )
2008-05-07 11:04:18 +02:00
report_output = query_to_dict ( cursor . fetchall ( ) , cursor . description )
2008-04-02 01:26:12 +02:00
data = [ ]
for record in report_output :
line = [ ]
for c in columns :
2010-01-20 21:00:16 +01:00
c = c . encode ( ' utf-8 ' )
2008-04-02 01:26:12 +02:00
line . append ( record [ c ] )
data . append ( line )
if report in ( ' queuemonth ' , ' usermonth ' ) :
chart_url = line_chart ( [ columns ] + data )
elif report in ( ' queuestatus ' , ' queuepriority ' , ' userstatus ' , ' userpriority ' ) :
chart_url = bar_chart ( [ columns ] + data )
else :
chart_url = ' '
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 , {
' headings ' : columns ,
' data ' : data ,
' chart ' : chart_url ,
2008-10-25 00:52:34 +02:00
' title ' : title ,
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 )