2007-12-27 01:29:17 +01:00
|
|
|
""" ..
|
|
|
|
.,::;::::::
|
|
|
|
..,::::::::,,,,::: Jutda Helpdesk - A Django
|
|
|
|
.,,::::::,,,,,,,,,,,,,:: powered ticket tracker for
|
|
|
|
.,::::::,,,,,,,,,,,,,,,,,,:;r. small enterprise
|
|
|
|
.::::,,,,,,,,,,,,,,,,,,,,,,:;;rr.
|
|
|
|
.:::,,,,,,,,,,,,,,,,,,,,,,,:;;;;;rr (c) Copyright 2008
|
|
|
|
.:::,,,,,,,,,,,,,,,,,,,,,,,:;;;:::;;rr
|
|
|
|
.:::,,,,,,,,,,,,,,,,,,,,. ,;;;::::::;;rr Jutda
|
|
|
|
.:::,,,,,,,,,,,,,,,,,,. .:;;:::::::::;;rr
|
|
|
|
.:::,,,,,,,,,,,,,,,. .;r;::::::::::::;r; All Rights Reserved
|
|
|
|
.:::,,,,,,,,,,,,,,, .;r;;:::::::::::;;:.
|
|
|
|
.:::,,,,,,,,,,,,,,,. .;r;;::::::::::::;:.
|
|
|
|
.;:,,,,,,,,,,,,,,, .,;rr;::::::::::::;:. This software is released
|
|
|
|
.,:,,,,,,,,,,,,,. .,:;rrr;;::::::::::::;;. under a limited-use license that
|
|
|
|
:,,,,,,,,,,,,,..:;rrrrr;;;::::::::::::;;. allows you to freely download this
|
|
|
|
:,,,,,,,:::;;;rr;;;;;;:::::::::::::;;, software from it's manufacturer and
|
|
|
|
::::;;;;;;;;;;;:::::::::::::::::;;, use it yourself, however you may not
|
|
|
|
.r;;;;:::::::::::::::::::::::;;;, distribute it. For further details, see
|
|
|
|
.r;::::::::::::::::::::;;;;;:, the enclosed LICENSE file.
|
|
|
|
.;;::::::::::::::;;;;;:,.
|
|
|
|
.;;:::::::;;;;;;:,. Please direct people who wish to download this
|
|
|
|
.r;;;;;;;;:,. software themselves to www.jutda.com.au.
|
|
|
|
,,,..
|
|
|
|
|
|
|
|
$Id$
|
|
|
|
|
|
|
|
"""
|
|
|
|
# Python imports
|
|
|
|
from datetime import datetime
|
|
|
|
|
|
|
|
# Django imports
|
|
|
|
from django.contrib.auth.models import User
|
|
|
|
from django.contrib.auth.decorators import login_required
|
2008-01-15 01:20:00 +01:00
|
|
|
from django.core.urlresolvers import reverse
|
2007-12-27 01:29:17 +01:00
|
|
|
from django.db.models import Q
|
2008-01-15 01:20:00 +01:00
|
|
|
from django.http import HttpResponseRedirect, Http404, HttpResponse
|
|
|
|
from django.shortcuts import render_to_response, get_object_or_404
|
|
|
|
from django.template import loader, Context, RequestContext
|
2007-12-27 01:29:17 +01:00
|
|
|
|
|
|
|
# Helpdesk imports
|
2008-01-16 01:26:24 +01:00
|
|
|
from helpdesk.forms import TicketForm, PublicTicketForm
|
2008-01-07 21:30:55 +01:00
|
|
|
from helpdesk.lib import send_multipart_mail
|
2008-01-15 01:20:00 +01:00
|
|
|
from helpdesk.models import Ticket, Queue, FollowUp, TicketChange, PreSetReply
|
2007-12-27 01:29:17 +01:00
|
|
|
|
|
|
|
def dashboard(request):
|
2008-01-16 01:26:24 +01:00
|
|
|
"""
|
|
|
|
This isn't always truly a "dashboard" view. If the user is not logged in, we
|
|
|
|
instead show the user a "public submission" form and a way to view existing
|
|
|
|
tickets.
|
|
|
|
"""
|
|
|
|
if request.user.is_authenticated():
|
|
|
|
tickets = Ticket.objects.filter(assigned_to=request.user).exclude(status=Ticket.CLOSED_STATUS)
|
|
|
|
unassigned_tickets = Ticket.objects.filter(assigned_to__isnull=True).exclude(status=Ticket.CLOSED_STATUS)
|
2007-12-27 01:29:17 +01:00
|
|
|
|
2008-01-16 01:26:24 +01:00
|
|
|
dash_tickets = []
|
|
|
|
for q in Queue.objects.all():
|
|
|
|
dash_tickets.append({
|
|
|
|
'queue': q,
|
|
|
|
'open': q.ticket_set.filter(Q(status=Ticket.OPEN_STATUS) | Q(status=Ticket.REOPENED_STATUS)).count(),
|
|
|
|
'resolved': q.ticket_set.filter(status=Ticket.RESOLVED_STATUS).count(),
|
|
|
|
})
|
|
|
|
|
|
|
|
return render_to_response('helpdesk/dashboard.html',
|
|
|
|
RequestContext(request, {
|
|
|
|
'user_tickets': tickets,
|
|
|
|
'unassigned_tickets': unassigned_tickets,
|
|
|
|
'dash_tickets': dash_tickets,
|
|
|
|
}))
|
|
|
|
else:
|
|
|
|
# Not a logged in user
|
|
|
|
if request.method == 'POST':
|
|
|
|
form = PublicTicketForm(request.POST)
|
|
|
|
form.fields['queue'].choices = [('', '--------')] + [[q.id, q.title] for q in Queue.objects.all()]
|
|
|
|
if form.is_valid():
|
|
|
|
ticket = form.save()
|
|
|
|
return HttpResponseRedirect('%s?ticket=%s&email=%s'% (reverse('helpdesk_public_view'), ticket.ticket_for_url, ticket.submitter_email))
|
|
|
|
else:
|
|
|
|
form = PublicTicketForm()
|
|
|
|
form.fields['queue'].choices = [('', '--------')] + [[q.id, q.title] for q in Queue.objects.all()]
|
|
|
|
|
|
|
|
return render_to_response('helpdesk/public_homepage.html',
|
|
|
|
RequestContext(request, {
|
|
|
|
'form': form,
|
|
|
|
}))
|
2007-12-27 01:29:17 +01:00
|
|
|
|
2008-01-15 05:00:19 +01:00
|
|
|
def delete_ticket(request, ticket_id):
|
|
|
|
ticket = get_object_or_404(Ticket, id=ticket_id)
|
|
|
|
|
|
|
|
if request.method == 'GET':
|
|
|
|
return render_to_response('helpdesk/delete_ticket.html',
|
|
|
|
RequestContext(request, {
|
|
|
|
'ticket': ticket,
|
|
|
|
}))
|
|
|
|
else:
|
|
|
|
ticket.delete()
|
|
|
|
return HttpResponseRedirect(reverse('helpdesk_home'))
|
|
|
|
delete_ticket = login_required(delete_ticket)
|
|
|
|
|
2007-12-27 01:29:17 +01:00
|
|
|
def view_ticket(request, ticket_id):
|
|
|
|
ticket = get_object_or_404(Ticket, id=ticket_id)
|
|
|
|
if request.GET.has_key('take'):
|
|
|
|
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:
|
|
|
|
if not ticket.assigned_to:
|
|
|
|
owner = 0
|
|
|
|
else:
|
|
|
|
owner = ticket.assigned_to.id
|
|
|
|
request.POST = {'new_status': Ticket.CLOSED_STATUS, 'public': 1, 'owner': owner, 'title': ticket.title, 'comment': "Accepted resolution and closed ticket"}
|
|
|
|
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,
|
|
|
|
'active_users': User.objects.filter(is_active=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)),
|
2007-12-27 01:29:17 +01:00
|
|
|
}))
|
|
|
|
view_ticket = login_required(view_ticket)
|
|
|
|
|
|
|
|
def update_ticket(request, ticket_id):
|
|
|
|
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))
|
|
|
|
|
2007-12-27 01:29:17 +01:00
|
|
|
if not owner and ticket.assigned_to:
|
|
|
|
owner = ticket.assigned_to.id
|
|
|
|
|
|
|
|
f = FollowUp(ticket=ticket, date=datetime.now(), comment=comment, user=request.user)
|
|
|
|
if public:
|
|
|
|
f.public = True
|
|
|
|
|
2008-01-21 02:02:12 +01:00
|
|
|
reassigned = False
|
|
|
|
|
2007-12-27 01:29:17 +01:00
|
|
|
if owner:
|
|
|
|
if owner != 0 and (ticket.assigned_to and owner != ticket.assigned_to.id) or not ticket.assigned_to:
|
|
|
|
new_user = User.objects.get(id=owner)
|
|
|
|
f.title = 'Assigned to %s' % new_user.username
|
|
|
|
ticket.assigned_to = new_user
|
2008-01-21 02:02:12 +01:00
|
|
|
reassigned = True
|
2007-12-27 01:29:17 +01:00
|
|
|
else:
|
|
|
|
f.title = 'Unassigned'
|
|
|
|
ticket.assigned_to = None
|
|
|
|
|
|
|
|
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:
|
|
|
|
f.title = 'Comment'
|
|
|
|
else:
|
|
|
|
f.title = 'Updated'
|
|
|
|
|
|
|
|
f.save()
|
|
|
|
|
|
|
|
if title != ticket.title:
|
|
|
|
c = TicketChange(followup=f, field='Title', old_value=ticket.title, new_value=title)
|
|
|
|
c.save()
|
|
|
|
ticket.title = title
|
|
|
|
|
2008-01-10 01:28:45 +01:00
|
|
|
if priority != ticket.priority:
|
|
|
|
c = TicketChange(followup=f, field='Priority', old_value=ticket.priority, new_value=priority)
|
|
|
|
c.save()
|
|
|
|
ticket.priority = priority
|
|
|
|
|
2008-01-07 21:22:13 +01:00
|
|
|
if f.new_status == Ticket.RESOLVED_STATUS:
|
|
|
|
ticket.resolution = comment
|
|
|
|
|
2008-01-15 00:40:51 +01:00
|
|
|
if public and ticket.submitter_email and f.comment != '':
|
2008-01-07 21:22:13 +01:00
|
|
|
context = {
|
|
|
|
'ticket': ticket,
|
|
|
|
'queue': ticket.queue,
|
|
|
|
'resolution': ticket.resolution,
|
|
|
|
'comment': f.comment,
|
|
|
|
}
|
|
|
|
if f.new_status == Ticket.RESOLVED_STATUS:
|
|
|
|
template = 'helpdesk/emails/submitter_resolved'
|
2008-01-07 21:30:55 +01:00
|
|
|
subject = '%s %s (Resolved)' % (ticket.ticket, ticket.title)
|
2008-01-21 02:02:12 +01:00
|
|
|
elif f.new_status == Ticket.CLOSED_STATUS:
|
|
|
|
template = 'helpdesk/emails/submitter_closed'
|
|
|
|
subject = '%s %s (Closed)' % (ticket.ticket, ticket.title)
|
2008-01-07 21:22:13 +01:00
|
|
|
else:
|
|
|
|
template = 'helpdesk/emails/submitter_updated'
|
2008-01-07 21:30:55 +01:00
|
|
|
subject = '%s %s (Updated)' % (ticket.ticket, ticket.title)
|
2008-01-07 21:22:13 +01:00
|
|
|
send_multipart_mail(template, context, subject, ticket.submitter_email, ticket.queue.from_address)
|
|
|
|
|
2008-01-21 02:02:12 +01:00
|
|
|
if ticket.assigned_to and request.user != ticket.assigned_to:
|
|
|
|
# We only send e-mails to staff members if the ticket is updated by
|
|
|
|
# another user.
|
|
|
|
if reassigned:
|
|
|
|
template_staff = 'helpdesk/emails/owner_assigned'
|
|
|
|
subject = '%s %s (Assigned)' % (ticket.ticket, ticket.title)
|
|
|
|
elif f.new_status == Ticket.RESOLVED_STATUS:
|
|
|
|
template_staff = 'helpdesk/emails/owner_resolved'
|
|
|
|
subject = '%s %s (Resolved)' % (ticket.ticket, ticket.title)
|
|
|
|
elif f.new_status == Ticket.CLOSED_STATUS:
|
|
|
|
template_staff = 'helpdesk/emails/owner_closed'
|
|
|
|
subject = '%s %s (Closed)' % (ticket.ticket, ticket.title)
|
|
|
|
else:
|
|
|
|
template_staff = 'helpdesk/emails/owner_updated'
|
|
|
|
subject = '%s %s (Updated)' % (ticket.ticket, ticket.title)
|
|
|
|
|
|
|
|
send_multipart_mail(template_staff, context, subject, ticket.submitter_email, ticket.queue.from_address)
|
|
|
|
|
|
|
|
|
2008-01-21 04:51:35 +01:00
|
|
|
if ticket.queue.updated_ticket_cc:
|
2008-01-21 02:02:12 +01:00
|
|
|
if reassigned:
|
|
|
|
template_cc = 'helpdesk/emails/cc_assigned'
|
|
|
|
subject = '%s %s (Assigned)' % (ticket.ticket, ticket.title)
|
|
|
|
elif f.new_status == Ticket.RESOLVED_STATUS:
|
|
|
|
template_cc = 'helpdesk/emails/cc_resolved'
|
|
|
|
subject = '%s %s (Resolved)' % (ticket.ticket, ticket.title)
|
|
|
|
elif f.new_status == Ticket.CLOSED_STATUS:
|
|
|
|
template_cc = 'helpdesk/emails/cc_closed'
|
|
|
|
subject = '%s %s (Closed)' % (ticket.ticket, ticket.title)
|
|
|
|
else:
|
|
|
|
template_cc = 'helpdesk/emails/cc_updated'
|
|
|
|
subject = '%s %s (Updated)' % (ticket.ticket, ticket.title)
|
|
|
|
|
2008-01-21 04:51:35 +01:00
|
|
|
send_multipart_mail(template_cc, context, subject, ticket.queue.updated_ticket_cc, ticket.queue.from_address)
|
2008-01-21 02:02:12 +01:00
|
|
|
|
2007-12-27 01:29:17 +01:00
|
|
|
ticket.save()
|
|
|
|
|
|
|
|
return HttpResponseRedirect(ticket.get_absolute_url())
|
|
|
|
update_ticket = login_required(update_ticket)
|
|
|
|
|
|
|
|
def ticket_list(request):
|
|
|
|
tickets = Ticket.objects.select_related()
|
|
|
|
context = {}
|
|
|
|
|
|
|
|
### FILTERING
|
|
|
|
queues = request.GET.getlist('queue')
|
|
|
|
if queues:
|
|
|
|
queues = [int(q) for q in queues]
|
|
|
|
tickets = tickets.filter(queue__id__in=queues)
|
|
|
|
context = dict(context, queues=queues)
|
|
|
|
|
|
|
|
owners = request.GET.getlist('assigned_to')
|
|
|
|
if owners:
|
|
|
|
owners = [int(u) for u in owners]
|
|
|
|
tickets = tickets.filter(assigned_to__id__in=owners)
|
|
|
|
context = dict(context, owners=owners)
|
|
|
|
|
|
|
|
statuses = request.GET.getlist('status')
|
|
|
|
if statuses:
|
|
|
|
statuses = [int(s) for s in statuses]
|
|
|
|
tickets = tickets.filter(status__in=statuses)
|
|
|
|
context = dict(context, statuses=statuses)
|
2008-01-07 21:22:13 +01: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)
|
|
|
|
)
|
|
|
|
tickets = tickets.filter(qset)
|
|
|
|
context = dict(context, query=q)
|
2007-12-27 01:29:17 +01:00
|
|
|
|
|
|
|
### SORTING
|
|
|
|
sort = request.GET.get('sort', None)
|
2008-01-10 06:06:47 +01:00
|
|
|
if sort not in ('status', 'assigned_to', 'created', 'title', 'queue', 'priority'):
|
2007-12-27 01:29:17 +01:00
|
|
|
sort = 'created'
|
|
|
|
tickets = tickets.order_by(sort)
|
|
|
|
context = dict(context, sort=sort)
|
|
|
|
|
|
|
|
return render_to_response('helpdesk/ticket_list.html',
|
|
|
|
RequestContext(request, dict(
|
|
|
|
context,
|
|
|
|
tickets=tickets,
|
|
|
|
user_choices=User.objects.filter(is_active=True),
|
|
|
|
queue_choices=Queue.objects.all(),
|
|
|
|
status_choices=Ticket.STATUS_CHOICES,
|
|
|
|
)))
|
|
|
|
ticket_list = login_required(ticket_list)
|
|
|
|
|
|
|
|
def create_ticket(request):
|
|
|
|
if request.method == 'POST':
|
|
|
|
form = TicketForm(request.POST)
|
|
|
|
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:
|
|
|
|
form = TicketForm()
|
|
|
|
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)]
|
|
|
|
|
|
|
|
return render_to_response('helpdesk/create_ticket.html',
|
|
|
|
RequestContext(request, {
|
|
|
|
'form': form,
|
|
|
|
}))
|
|
|
|
create_ticket = login_required(create_ticket)
|
2008-01-11 02:00:01 +01:00
|
|
|
|
|
|
|
def raw_details(request, type):
|
|
|
|
if not type in ('preset',):
|
|
|
|
raise Http404
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
raise Http404
|
|
|
|
raw_details = login_required(raw_details)
|
2008-01-16 01:26:24 +01:00
|
|
|
|
|
|
|
def public_view(request):
|
|
|
|
ticket = request.GET.get('ticket', '')
|
|
|
|
email = request.GET.get('email', '')
|
|
|
|
error_message = ''
|
|
|
|
|
|
|
|
if ticket and email:
|
|
|
|
queue, ticket_id = ticket.split('-')
|
|
|
|
try:
|
|
|
|
t = Ticket.objects.get(id=ticket_id, queue__slug__iexact=queue, submitter_email__iexact=email)
|
|
|
|
return render_to_response('helpdesk/public_view_ticket.html',
|
|
|
|
RequestContext(request, {'ticket': t,}))
|
|
|
|
except:
|
|
|
|
t = False;
|
|
|
|
error_message = 'Invalid ticket ID or e-mail address. Please try again.'
|
|
|
|
|
|
|
|
return render_to_response('helpdesk/public_view_form.html',
|
|
|
|
RequestContext(request, {
|
|
|
|
'ticket': ticket,
|
|
|
|
'email': email,
|
|
|
|
'error_message': error_message,
|
|
|
|
}))
|
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-01-21 00:31:27 +01:00
|
|
|
title = 'Ticket taken off hold'
|
2008-01-16 05:52:30 +01:00
|
|
|
else:
|
|
|
|
ticket.on_hold = True
|
2008-01-21 00:31:27 +01:00
|
|
|
title = 'Ticket placed on hold'
|
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()
|
|
|
|
|
|
|
|
ticket.save()
|
|
|
|
|
|
|
|
return HttpResponseRedirect(ticket.get_absolute_url())
|
|
|
|
hold_ticket = login_required(hold_ticket)
|
|
|
|
|
|
|
|
def unhold_ticket(request, ticket_id):
|
|
|
|
return hold_ticket(request, ticket_id, unhold=True)
|
|
|
|
unhold_ticket = login_required(unhold_ticket)
|
|
|
|
|