* Removed e-mail templates to the database

* Added statistics views, making use of Google Charts
* Added initial data fixture, to automatically create e-mail templates
  at install-time
This commit is contained in:
Ross Poulton 2008-04-01 23:26:12 +00:00
parent 47afa9b45b
commit 7dfb38eab9
49 changed files with 443 additions and 499 deletions

22
api.py
View File

@ -20,7 +20,7 @@ from django.shortcuts import render_to_response
from django.template import loader, Context from django.template import loader, Context
from django import newforms as forms from django import newforms as forms
from helpdesk.lib import send_multipart_mail from helpdesk.lib import send_templated_mail
from helpdesk.models import Ticket, Queue, FollowUp from helpdesk.models import Ticket, Queue, FollowUp
from helpdesk.forms import TicketForm from helpdesk.forms import TicketForm
@ -174,19 +174,14 @@ class API:
'comment': f.comment, 'comment': f.comment,
} }
subject = '%s %s (Updated)' % (ticket.ticket, ticket.title)
if public and ticket.submitter_email: if public and ticket.submitter_email:
template = 'helpdesk/emails/submitter_updated' send_templated_mail('updated_submitter', context, recipients=ticket.submitter_email, sender=ticket.queue.from_address, fail_silently=True)
send_multipart_mail(template, context, subject, ticket.submitter_email, ticket.queue.from_address)
if ticket.queue.updated_ticket_cc: if ticket.queue.updated_ticket_cc:
template_cc = 'helpdesk/emails/cc_updated' send_templated_mail('updated_cc', context, recipients=ticket.queue.updated_ticket_cc, sender=ticket.queue.from_address, fail_silently=True)
send_multipart_mail(template_cc, context, subject, q.updated_ticket_cc, ticket.queue.from_address)
if ticket.assigned_to and self.request.user != ticket.assigned_to: if ticket.assigned_to and self.request.user != ticket.assigned_to:
template_owner = 'helpdesk/emails/owner_updated' send_templated_mail('updated_owner', context, recipients=ticket.assigned_to.email, sender=ticket.queue.from_address, fail_silently=True)
send_multipart_mail(template_owner, context, subject, t.assigned_to.email, ticket.queue.from_address)
ticket.save() ticket.save()
@ -216,16 +211,13 @@ class API:
subject = '%s %s (Resolved)' % (ticket.ticket, ticket.title) subject = '%s %s (Resolved)' % (ticket.ticket, ticket.title)
if ticket.submitter_email: if ticket.submitter_email:
template = 'helpdesk/emails/submitter_resolved' send_templated_mail('resolved_submitter', context, recipients=ticket.submitter_email, sender=ticket.queue.from_address, fail_silently=True)
send_multipart_mail(template, context, subject, ticket.submitter_email, ticket.queue.from_address)
if ticket.queue.updated_ticket_cc: if ticket.queue.updated_ticket_cc:
template_cc = 'helpdesk/emails/cc_resolved' send_templated_mail('resolved_cc', context, recipients=ticket.queue.updated_ticket_cc, sender=ticket.queue.from_address, fail_silently=True)
send_multipart_mail(template_cc, context, subject, q.updated_ticket_cc, ticket.queue.from_address)
if ticket.assigned_to and self.request.user != ticket.assigned_to: if ticket.assigned_to and self.request.user != ticket.assigned_to:
template_owner = 'helpdesk/emails/owner_resolved' send_templated_mail('resolved_resolved', context, recipients=ticket.assigned_to.email, sender=ticket.queue.from_address, fail_silently=True)
send_multipart_mail(template_owner, context, subject, t.assigned_to.email, ticket.queue.from_address)
ticket.resoltuion = f.comment ticket.resoltuion = f.comment
ticket.status = Ticket.RESOLVED_STATUS ticket.status = Ticket.RESOLVED_STATUS

File diff suppressed because one or more lines are too long

View File

@ -77,19 +77,19 @@ class TicketForm(forms.Form):
'queue': q, 'queue': q,
} }
from helpdesk.lib import send_multipart_mail from helpdesk.lib import send_templated_mail
if t.submitter_email: if t.submitter_email:
send_multipart_mail('helpdesk/emails/submitter_newticket', context, '%s %s' % (t.ticket, t.title), t.submitter_email, q.from_address) send_templated_mail('newticket_submitter', context, recipients=t.submitter_email, sender=q.from_address, fail_silently=True)
if t.assigned_to and t.assigned_to != user: if t.assigned_to and t.assigned_to != user:
send_multipart_mail('helpdesk/emails/owner_assigned', context, '%s %s (Opened)' % (t.ticket, t.title), t.assigned_to.email, q.from_address) send_templated_mail('assigned_owner', context, recipients=t.assigned_to.email, sender=q.from_address, fail_silently=True)
if q.new_ticket_cc: if q.new_ticket_cc:
send_multipart_mail('helpdesk/emails/cc_newticket', context, '%s %s (Opened)' % (t.ticket, t.title), q.updated_ticket_cc, q.from_address) send_templated_mail('newticket_cc', context, recipients=q.new_ticket_cc, sender=q.from_address, fail_silently=True)
if q.updated_ticket_cc and q.updated_ticket_cc != q.new_ticket_cc: if q.updated_ticket_cc and q.updated_ticket_cc != q.new_ticket_cc:
send_multipart_mail('helpdesk/emails/cc_newticket', context, '%s %s (Opened)' % (t.ticket, t.title), q.updated_ticket_cc, q.from_address) send_templated_mail('newticket_cc', context, recipients=q.updated_ticket_cc, sender=q.from_address, fail_silently=True)
return t return t
@ -146,13 +146,13 @@ class PublicTicketForm(forms.Form):
'queue': q, 'queue': q,
} }
from helpdesk.lib import send_multipart_mail from helpdesk.lib import send_templated_mail
send_multipart_mail('helpdesk/emails/submitter_newticket', context, '%s %s' % (t.ticket, t.title), t.submitter_email, q.from_address) send_templated_mail('newticket_submitter', context, recipients=t.submitter_email, sender=q.from_address, fail_silently=True)
if q.new_ticket_cc: if q.new_ticket_cc:
send_multipart_mail('helpdesk/emails/cc_newticket', context, '%s %s (Opened)' % (t.ticket, t.title), q.updated_ticket_cc, q.from_address) send_templated_mail('newticket_cc', context, recipients=q.new_ticket_cc, sender=q.from_address, fail_silently=True)
if q.updated_ticket_cc and q.updated_ticket_cc != q.new_ticket_cc: if q.updated_ticket_cc and q.updated_ticket_cc != q.new_ticket_cc:
send_multipart_mail('helpdesk/emails/cc_newticket', context, '%s %s (Opened)' % (t.ticket, t.title), q.updated_ticket_cc, q.from_address) send_templated_mail('newticket_cc', context, recipients=q.updated_ticket_cc, sender=q.from_address, fail_silently=True)
return t return t

View File

@ -11,7 +11,7 @@ table {
} }
#container { #container {
width: 600px; width: 700px;
margin: 0 auto; margin: 0 auto;
text-align: left; text-align: left;
} }
@ -26,7 +26,34 @@ table {
#header li { #header li {
display: inline; display: inline;
float: left; float: left;
padding-left: 5px; }
#header li a {
padding: 2px 3px;
margin: 2px;
border: solid #444 1px;
color: #000;
text-decoration: none;
font-size: 10pt;
line-height: 12pt;
}
#searchform .input {
border: solid #444 1px;
background-color: #fff;
color: #ccc;
padding: 2px 3px;
margin: 0px 2px;
font-size: 10pt;
line-height: 12pt;
}
#searchform .input:focus {
color: #000;
}
#header li a:hover {
background-color: #ccc;
} }
#header, #body, #footer { #header, #body, #footer {

121
lib.py
View File

@ -5,6 +5,39 @@ Jutda Helpdesk - A Django powered ticket tracker for small enterprise.
lib.py - Common functions (eg multipart e-mail) lib.py - Common functions (eg multipart e-mail)
""" """
def send_templated_mail(template_name, email_context, recipients, sender=None, bcc=None, fail_silently=False, files=None):
from helpdesk.models import EmailTemplate
from django.core.mail import EmailMultiAlternatives
from django.template import loader, Context
from django.conf import settings
t = EmailTemplate.objects.get(template_name__iexact=template_name)
if not sender:
sender = settings.DEFAULT_FROM_EMAIL
context = Context(email_context)
text_part = loader.get_template_from_string("%s{%% include 'helpdesk/email_text_footer.txt' %%}" % t.plain_text).render(context)
html_part = loader.get_template_from_string("{%% extends 'helpdesk/email_html_base.html' %%}{%% block title %%}%s{%% endblock %%}{%% block content %%}%s{%% endblock %%}" % (t.heading, t.html)).render(context)
subject_part = loader.get_template_from_string("{{ ticket.ticket }} {{ ticket.title }} %s" % t.subject).render(context)
if type(recipients) != list:
recipients = [recipients,]
msg = EmailMultiAlternatives(subject_part, text_part, sender, recipients, bcc=bcc)
msg.attach_alternative(html_part, "text/html")
if files:
if type(files) != list:
files = [files,]
for file in files:
msg.attach_file(file)
return msg.send(fail_silently)
def send_multipart_mail(template_name, email_context, subject, recipients, sender=None, bcc=None, fail_silently=False, files=None): def send_multipart_mail(template_name, email_context, subject, recipients, sender=None, bcc=None, fail_silently=False, files=None):
""" """
@ -58,14 +91,96 @@ def send_multipart_mail(template_name, email_context, subject, recipients, sende
return msg.send(fail_silently) return msg.send(fail_silently)
def normalise_to_100(data): def normalise_data(data, to=100):
""" """
Used for normalising data prior to graphing with Google charting API Used for normalising data prior to graphing with Google charting API
""" """
max_value = max(data) max_value = max(data)
if max_value > 100: if max_value > to:
new_data = [] new_data = []
for d in data: for d in data:
new_data.append(int(d/float(max_value)*100)) new_data.append(int(d/float(max_value)*to))
data = new_data data = new_data
return data return data
chart_colours = ('80C65A', '990066', 'FF9900', '3399CC', 'BBCCED', '3399CC', 'FFCC33')
def line_chart(data):
"""
'data' is a list of lists making a table.
Row 1, columns 2-n are data headings (the time periods)
Rows 2-n are data, with column 1 being the line labels
"""
column_headings = data[0][1:]
max = 0
for row in data[1:]:
for field in row[1:]:
if field > max:
max = field
# Set width to '65px * number of months'.
chart_url = 'http://chart.apis.google.com/chart?cht=lc&chs=%sx90&chd=t:' % (len(column_headings)*65)
first_row = True
row_headings = []
for row in data[1:]:
# Add data to URL, normalised to the maximum for all lines on this chart
norm = normalise_data(row[1:], max)
if not first_row:
chart_url += '|'
chart_url += ','.join([str(num) for num in norm])
row_headings.append(row[0])
first_row = False
chart_url += '&chds='
rows = len(data)-1
first = True
for row in range(rows):
# Set maximum data ranges to '0:x' where 'x' is the maximum number in use.
if not first:
chart_url += ','
chart_url += '0,%s' % max
first = False
chart_url += '&chdl=%s' % '|'.join(row_headings) # Display legend/labels
chart_url += '&chco=%s' % ','.join(chart_colours) # Default colour set
chart_url += '&chxt=x,y' # Turn on axis labels
chart_url += '&chxl=0:|%s|1:|0|%s' % ('|'.join(column_headings), max) # Axis Label Text
return chart_url
def bar_chart(data):
"""
'data' is a list of lists making a table.
Row 1, columns 2-n are data headings
Rows 2-n are data, with column 1 being the line labels
"""
column_headings = data[0][1:]
max = 0
for row in data[1:]:
for field in row[1:]:
if field > max:
max = field
# Set width to '150px * number of months'.
chart_url = 'http://chart.apis.google.com/chart?cht=bvg&chs=%sx90&chd=t:' % (len(column_headings) * 150)
first_row = True
row_headings = []
for row in data[1:]:
# Add data to URL, normalised to the maximum for all lines on this chart
norm = normalise_data(row[1:], max)
if not first_row:
chart_url += '|'
chart_url += ','.join([str(num) for num in norm])
row_headings.append(row[0])
first_row = False
chart_url += '&chds=0,%s' % max
chart_url += '&chdl=%s' % '|'.join(row_headings) # Display legend/labels
chart_url += '&chco=%s' % ','.join(chart_colours) # Default colour set
chart_url += '&chxt=x,y' # Turn on axis labels
chart_url += '&chxl=0:|%s|1:|0|%s' % ('|'.join(column_headings), max) # Axis Label Text
return chart_url

View File

@ -94,11 +94,11 @@ class Ticket(models.Model):
) )
PRIORITY_CHOICES = ( PRIORITY_CHOICES = (
(1, '1 (Critical)'), (1, '1. Critical'),
(2, '2 (High)'), (2, '2. High'),
(3, '3 (Normal)'), (3, '3. Normal'),
(4, '4 (Low)'), (4, '4. Low'),
(5, '5 (Very Low)'), (5, '5. Very Low'),
) )
title = models.CharField(maxlength=200) title = models.CharField(maxlength=200)
@ -342,3 +342,24 @@ class EscalationExclusion(models.Model):
def __unicode__(self): def __unicode__(self):
return u'%s' % self.name return u'%s' % self.name
class EmailTemplate(models.Model):
"""
Since these are more likely to be changed than other templates, we store
them in the database.
"""
template_name = models.CharField(maxlength=100, unique=True)
subject = models.CharField(maxlength=100, help_text=u'This will be prefixed with "[ticket.ticket] ticket.title". We recommend something simple such as "(Updated") or "(Closed)" - the same context is available as in plain_text, below.')
heading = models.CharField(maxlength=100, help_text=u'In HTML e-mails, this will be the heading at the top of the email - the same context is available as in plain_text, below.')
plain_text = models.TextField(help_text=u'The context available to you includes {{ ticket }}, {{ queue }}, and depending on the time of the call: {{ resolution }} or {{ comment }}.')
html = models.TextField(help_text=u'The same context is available here as in plain_text, above.')
class Admin:
pass
def __unicode__(self):
return u'%s' % self.template_name
class Meta:
ordering = ['template_name',]

View File

@ -10,7 +10,7 @@ scripts/escalate_tickets.py - Easy way to escalate tickets based on their age,
from datetime import datetime, timedelta, date from datetime import datetime, timedelta, date
from django.db.models import Q from django.db.models import Q
from helpdesk.models import Queue, Ticket, FollowUp, EscalationExclusion, TicketChange from helpdesk.models import Queue, Ticket, FollowUp, EscalationExclusion, TicketChange
from helpdesk.lib import send_multipart_mail from helpdesk.lib import send_templated_mail
import sys, getopt import sys, getopt
def escalate_tickets(queues, verbose): def escalate_tickets(queues, verbose):
@ -48,13 +48,13 @@ def escalate_tickets(queues, verbose):
} }
if t.submitter_email: if t.submitter_email:
send_multipart_mail('helpdesk/emails/submitter_escalated', context, '%s %s' % (t.ticket, t.title), t.submitter_email, t.queue.from_address) send_templated_mail('escalated_submitter', context, recipients=t.submitter_email, sender=t.queue.from_address, fail_silently=True)
if t.queue.updated_ticket_cc: if t.queue.updated_ticket_cc:
send_multipart_mail('helpdesk/emails/cc_escalated', context, '%s %s' % (t.ticket, t.title), t.queue.updated_ticket_cc, t.queue.from_address) send_templated_mail('escalated_cc', context, recipients=t.queue.updated_ticket_cc, sender=t.queue.from_address, fail_silently=True)
if t.assigned_to: if t.assigned_to:
send_multipart_mail('helpdesk/emails/owner_escalated', context, '%s %s' % (t.ticket, t.title), t.assigned_to, t.queue.from_address) send_templated_mail('escalated_owner', context, recipients=t.assigned_to.email, sender=t.queue.from_address, fail_silently=True)
if verbose: if verbose:
print " - Esclating %s from %s>%s" % (t.ticket, t.priority+1, t.priority) print " - Esclating %s from %s>%s" % (t.ticket, t.priority+1, t.priority)

View File

@ -15,7 +15,7 @@ from datetime import datetime, timedelta
import email, mimetypes, re import email, mimetypes, re
from email.Utils import parseaddr from email.Utils import parseaddr
from helpdesk.models import Queue, Ticket, FollowUp, Attachment from helpdesk.models import Queue, Ticket, FollowUp, Attachment
from helpdesk.lib import send_multipart_mail from helpdesk.lib import send_templated_mail
def process_email(): def process_email():
for q in Queue.objects.filter(email_box_type__isnull=False): for q in Queue.objects.filter(email_box_type__isnull=False):
@ -138,22 +138,22 @@ def ticket_from_message(message, queue):
if new: if new:
if sender_email: if sender_email:
send_multipart_mail('helpdesk/emails/submitter_newticket', context, '%s %s' % (t.ticket, t.title), sender_email, queue.from_address, fail_silently=True) send_templated_mail('newticket_submitter', context, recipients=sender_email, sender=queue.from_address, fail_silently=True)
if queue.new_ticket_cc: if queue.new_ticket_cc:
send_multipart_mail('helpdesk/emails/cc_newticket', context, '%s %s (Opened)' % (t.ticket, t.title), queue.updated_ticket_cc, queue.from_address, fail_silently=True) send_templated_mail('newticket_cc', context, recipients=queue.new_ticket_cc, sender=queue.from_address, fail_silently=True)
if queue.updated_ticket_cc and queue.updated_ticket_cc != queue.new_ticket_cc: if queue.updated_ticket_cc and queue.updated_ticket_cc != queue.new_ticket_cc:
send_multipart_mail('helpdesk/emails/cc_newticket', context, '%s %s (Opened)' % (t.ticket, t.title), queue.updated_ticket_cc, queue.from_address, fail_silently=True) send_templated_mail('newticket_cc', context, recipients=queue.updated_ticket_cc, sender=queue.from_address, fail_silently=True)
else: else:
update = " (Updated)" update = " (Updated)"
if t.assigned_to: if t.assigned_to:
send_multipart_mail('helpdesk/emails/owner_updated', context, '%s %s (Updated)' % (t.ticket, t.title), t.assigned_to.email, queue.from_address, file_silently=True) send_templated_mail('updated_owner', context, recipients=t.assigned_to.email, sender=queue.from_address, fail_silently=True)
if queue.updated_ticket_cc: if queue.updated_ticket_cc:
send_multipart_mail('helpdesk/emails/cc_updated', context, '%s %s (Updated)' % (t.ticket, t.title), queue.updated_ticket_cc, queue.from_address, fail_silently=True) send_templated_mail('updated_cc', context, recipients=queue.updated_ticket_cc, sender=queue.from_address, fail_silently=True)
f = FollowUp( f = FollowUp(
ticket = t, ticket = t,

View File

@ -12,9 +12,10 @@
<ul> <ul>
<li><a href='{% url helpdesk_home %}'>Dashboard</a></li> <li><a href='{% url helpdesk_home %}'>Dashboard</a></li>
<li><a href='{% url helpdesk_list %}'>Tickets</a></li> <li><a href='{% url helpdesk_list %}'>Tickets</a></li>
<li><a href='{% url helpdesk_submit %}'>Submit Ticket</a></li> <li><a href='{% url helpdesk_submit %}'>New Ticket</a></li>
<li><a href='{% url helpdesk_report_index %}'>Stats</a></li>
<li><a href='{% url logout %}'>Logout</a></li> <li><a href='{% url logout %}'>Logout</a></li>
{% if not query %}<li><form method='get' action='{% url helpdesk_list %}'><input type='text' name='q' size='10' /><input type='hidden' name='status' value='1' /><input type='hidden' name='status' value='2' /><input type='hidden' name='status' value='3' /><input type='submit' value='Search' /></form></li>{% endif %} {% if not query %}<li><form id='searchform' method='get' action='{% url helpdesk_list %}'><input type='text' name='q' size='10' class='input' value='Search...' id='search_query' onFocus='s = document.getElementById("search_query");if (s.value == "Search...") { s.value = ""; }' /><input type='hidden' name='status' value='1' /><input type='hidden' name='status' value='2' /><input type='hidden' name='status' value='3' /></form></li>{% endif %}
</ul> </ul>
</div> </div>
<div id='body'> <div id='body'>

View File

@ -9,9 +9,9 @@
<tr class='row_columnheads'><th>Queue</th><th>Open</th><th>Resolved</th></tr> <tr class='row_columnheads'><th>Queue</th><th>Open</th><th>Resolved</th></tr>
{% for queue in dash_tickets %} {% for queue in dash_tickets %}
<tr class='row_{% cycle odd,even %} row_hover '> <tr class='row_{% cycle odd,even %} row_hover '>
<th><a href='{% url helpdesk_list %}?queue={{ queue.queue.id }}'>{{ queue.queue }}</a></th> <th><a href='{% url helpdesk_list %}?queue={{ queue.queue }}'>{{ queue.name }}</a></th>
<td>{% if queue.open %}<a href='{% url helpdesk_list %}?queue={{ queue.queue.id }}&status=1&status=2'>{% endif %}{{ queue.open }}{% if queue.open %}</a>{% endif %}</td> <td>{% if queue.open %}<a href='{% url helpdesk_list %}?queue={{ queue.queue }}&status=1&status=2'>{% endif %}{{ queue.open }}{% if queue.open %}</a>{% endif %}</td>
<td>{% if queue.resolved %}<a href='{% url helpdesk_list %}?queue={{ queue.queue.id }}&status=3'>{% endif %}{{ queue.resolved }}{% if queue.resolved %}</a>{% endif %}</td> <td>{% if queue.resolved %}<a href='{% url helpdesk_list %}?queue={{ queue.queue }}&status=3'>{% endif %}{{ queue.resolved }}{% if queue.resolved %}</a>{% endif %}</td>
</tr> </tr>
{% endfor %} {% endfor %}
</table> </table>

View File

@ -1,10 +0,0 @@
{% extends "helpdesk/emails/base.html" %}
{% block header %}Ticket Re-Assigned{% endblock %}
{% block content %}
<p style='font-family: "Trebuchet MS", Arial, sans-serif; font-size: 11pt;'>Hello,</p>
<p style='font-family: "Trebuchet MS", Arial, sans-serif; font-size: 11pt;'>This is a courtesy e-mail to let you know that ticket <a href='{{ ticket.staff_url }}'><b>{{ ticket.ticket }}</b></a> (<em>{{ ticket.title }}</em>) has been {% if ticket.assigned_to %}assigned to {{ ticket.assigned_to %}{% else %}unassigned{% endif %}.</p>
{% endblock %}

View File

@ -1,7 +0,0 @@
Hello,
This is a courtesy e-mail to let you know that ticket {{ ticket.ticket }} ("{{ ticket.title }}") has been {% if ticket.assigned_to %}assigned to {{ ticket.assigned_to }}{% else %}unassigned{% endif %}
You can view this online at {{ ticket.staff_url }}.
{% include "helpdesk/emails/text_footer.txt" %}

View File

@ -1,12 +0,0 @@
{% extends "helpdesk/emails/base.html" %}
{% block header %}Ticket Closed{% endblock %}
{% block content %}
<p style='font-family: "Trebuchet MS", Arial, sans-serif; font-size: 11pt;'>Hello,</p>
<p style='font-family: "Trebuchet MS", Arial, sans-serif; font-size: 11pt;'>Ticket <i>{{ ticket.title }}</i> ("{{ ticket.title }}") has been closed.</p>
<p style='font-family: "Trebuchet MS", Arial, sans-serif; font-size: 11pt;'>If you wish to view this ticket online, you can visit <a href="{{ ticket.get_staff_url }}">{{ ticket.get_staff_url }}</a>.</p>
{% endblock %}

View File

@ -1,7 +0,0 @@
Hello,
Ticket {{ ticket.title }} ("{{ ticket.title }}") has been closed.
If you wish to view this ticket online, you can visit {{ ticket.get_staff_url }}.
{% include "helpdesk/emails/text_footer.txt" %}

View File

@ -1,10 +0,0 @@
{% extends "helpdesk/emails/base.html" %}
{% block header %}Ticket Escalated{% endblock %}
{% block content %}
<p style='font-family: "Trebuchet MS", Arial, sans-serif; font-size: 11pt;'>Hello,</p>
<p style='font-family: "Trebuchet MS", Arial, sans-serif; font-size: 11pt;'>This is a courtesy e-mail to let you know that ticket <a href='{{ ticket.staff_url }}'><b>{{ ticket.ticket }}</b></a> (<em>{{ ticket.title }}</em>) has been escalated automatically.</p>
{% endblock %}

View File

@ -1,7 +0,0 @@
Hello,
This is a courtesy e-mail to let you know that ticket {{ ticket.ticket }} ("{{ ticket.title }}") has been escalated automatically.
You can view this online at {{ ticket.staff_url }}.
{% include "helpdesk/emails/text_footer.txt" %}

View File

@ -1,10 +0,0 @@
{% extends "helpdesk/emails/base.html" %}
{% block header %}Ticket Re-Assigned{% endblock %}
{% block content %}
<p style='font-family: "Trebuchet MS", Arial, sans-serif; font-size: 11pt;'>Hello,</p>
<p style='font-family: "Trebuchet MS", Arial, sans-serif; font-size: 11pt;'>This is a courtesy e-mail to let you know that ticket <a href='{{ ticket.staff_url }}'><b>{{ ticket.ticket }}</b></a> (<em>{{ ticket.title }}</em>) has been opened.</p>
{% endblock %}

View File

@ -1,7 +0,0 @@
Hello,
This is a courtesy e-mail to let you know that ticket {{ ticket.ticket }} ("{{ ticket.title }}") has been opened.
You can view this online at {{ ticket.staff_url }}.
{% include "helpdesk/emails/text_footer.txt" %}

View File

@ -1,18 +0,0 @@
{% extends "helpdesk/emails/base.html" %}
{% block header %}Ticket Resolution{% endblock %}
{% block content %}
<p style='font-family: "Trebuchet MS", Arial, sans-serif; font-size: 11pt;'>Hello,</p>
<p style='font-family: "Trebuchet MS", Arial, sans-serif; font-size: 11pt;'>Ticket {{ ticket.ticket }} (<i>{{ ticket.title }}</i>) has been resolved.</p>
<p style='font-family: "Trebuchet MS", Arial, sans-serif; font-size: 11pt;'>The following resolution was added:</p>
<blockquote style='font-family: "Trebuchet MS", Arial, sans-serif; font-size: 11pt; padding-left: 20px; border-left: solid #ccc 2px;'>{{ resolution }}</blockquote>
<p style='font-family: "Trebuchet MS", Arial, sans-serif; font-size: 11pt;'>This resolution has been e-mailed to the submitter, who will verify it before you can close this ticket.</p>
<p style='font-family: "Trebuchet MS", Arial, sans-serif; font-size: 11pt;'>If you wish to view this ticket online, you can visit <a href="{{ ticket.get_staff_url }}">{{ ticket.get_staff_url }}</a>.</p>
{% endblock %}

View File

@ -1,13 +0,0 @@
Hello,
Ticket {{ ticket.ticket }} ({{ ticket.title }}) has been resolved.
The following resolution was added:
{{ resolution }}
This resolution has been e-mailed to the submitter, who will verify it before you can close this ticket.
If you wish to view this ticket online, you can visit {{ ticket.get_staff_url }}.
{% include "helpdesk/emails/text_footer.txt" %}

View File

@ -1,18 +0,0 @@
{% extends "helpdesk/emails/base.html" %}
{% block header %}Ticket Updated{% endblock %}
{% block content %}
<p style='font-family: "Trebuchet MS", Arial, sans-serif; font-size: 11pt;'>Hello,</p>
<p style='font-family: "Trebuchet MS", Arial, sans-serif; font-size: 11pt;'>Ticket <a href='{{ ticket.staff_url }}'>{{ ticket.ticket }}</a> (<em>{{ ticket.title }}</em>) has been updated.</p>
<p style='font-family: "Trebuchet MS", Arial, sans-serif; font-size: 11pt;'>The following comment was added:</p>
<blockquote style='font-family: "Trebuchet MS", Arial, sans-serif; font-size: 11pt; padding-left: 20px; border-left: solid #ccc 2px;'>{{ comment }}</blockquote>
<p style='font-family: "Trebuchet MS", Arial, sans-serif; font-size: 11pt;'>This information has {% if private %}not {% endif %} been e-mailed to the submitter.</p>
<p style='font-family: "Trebuchet MS", Arial, sans-serif; font-size: 11pt;'>If you wish to view this ticket online, you can visit <a href="{{ ticket.staff_url }}">{{ ticket.staff_url }}</a>.</p>
{% endblock %}

View File

@ -1,13 +0,0 @@
Hello,
Ticket {{ ticket.ticket }} ({{ ticket.title }}) has been updated.
The following comment was added:
{{ comment }}
This information has {% if private %}not {% endif %} been e-mailed to the submitter.
If you wish to view this ticket online, you can visit {{ ticket.staff_url }}.
{% include "helpdesk/emails/text_footer.txt" %}

View File

@ -1,15 +0,0 @@
{% extends "helpdesk/emails/base.html" %}
{% block header %}Ticket Assigned To You{% endblock %}
{% block content %}
<p style='font-family: "Trebuchet MS", Arial, sans-serif; font-size: 11pt;'>Hello,</p>
<p style='font-family: "Trebuchet MS", Arial, sans-serif; font-size: 11pt;'>This is a courtesy e-mail to let you know that a ticket has been assigned to you.</p>
<p style='font-family: "Trebuchet MS", Arial, sans-serif; font-size: 11pt;'>{% if ticket.submitter_email %}The ticket was submitted by {{ ticket.submitter_email }}, and the subject{% else %}The ticket subject{% endif%} is <i>{{ ticket.title }}</i>.</p>
<p style='font-family: "Trebuchet MS", Arial, sans-serif; font-size: 11pt;'>The ticket ID is <a href='{{ ticket.staff_url }}'><b>{{ ticket.ticket }}</a></b>. It's priority is {{ ticket.get_priority_display }}.</p>
{% endblock %}

View File

@ -1,9 +0,0 @@
Hello,
This is a courtesy e-mail to let you know that a ticket has been assigned to you.
{% if ticket.submitter_email %}The ticket was submitted by {{ ticket.submitter_email }}, and the subject{% else %}The ticket subject{% endif%} is {{ ticket.title }}.
The ticket ID is {{ ticket.ticket }}, you can view it online at {{ ticket.staff_url }}. It's priority is {{ ticket.get_priority_display }}.
{% include "helpdesk/emails/text_footer.txt" %}

View File

@ -1,12 +0,0 @@
{% extends "helpdesk/emails/base.html" %}
{% block header %}Ticket Closed{% endblock %}
{% block content %}
<p style='font-family: "Trebuchet MS", Arial, sans-serif; font-size: 11pt;'>Hello,</p>
<p style='font-family: "Trebuchet MS", Arial, sans-serif; font-size: 11pt;'>A ticket currently assigned to you with a subject of <i>{{ ticket.title }}</i> has been closed.</p>
<p style='font-family: "Trebuchet MS", Arial, sans-serif; font-size: 11pt;'>If you wish to view this ticket online, you can visit <a href="{{ ticket.get_staff_url }}">{{ ticket.get_staff_url }}</a>.</p>
{% endblock %}

View File

@ -1,7 +0,0 @@
Hello,
A ticket currently assigned to you with a subject of "{{ ticket.title }}" has been closed.
If you wish to view this ticket online, you can visit {{ ticket.get_staff_url }}.
{% include "helpdesk/emails/text_footer.txt" %}

View File

@ -1,14 +0,0 @@
{% extends "helpdesk/emails/base.html" %}
{% block header %}Ticket Escalated{% endblock %}
{% block content %}
<p style='font-family: "Trebuchet MS", Arial, sans-serif; font-size: 11pt;'>Hello,</p>
<p style='font-family: "Trebuchet MS", Arial, sans-serif; font-size: 11pt;'>A ticket currently assigned to you with a subject of <i>{{ ticket.title }}</i> has been automatically escalated.</p>
<p style='font-family: "Trebuchet MS", Arial, sans-serif; font-size: 11pt;'>Please review this ticket and attempt to provide a resolution as soon as possible.</p>
<p style='font-family: "Trebuchet MS", Arial, sans-serif; font-size: 11pt;'>If you wish to view this ticket online, you can visit <a href="{{ ticket.get_staff_url }}">{{ ticket.get_staff_url }}</a>.</p>
{% endblock %}

View File

@ -1,9 +0,0 @@
Hello,
A ticket currently assigned to you with a subject of "{{ ticket.title }}" has been automatically escalated.
Please review this ticket and attempt to provide a resolution as soon as possible.
If you wish to view this ticket online, you can visit {{ ticket.get_staff_url }}.
{% include "helpdesk/emails/text_footer.txt" %}

View File

@ -1,18 +0,0 @@
{% extends "helpdesk/emails/base.html" %}
{% block header %}Ticket Resolution{% endblock %}
{% block content %}
<p style='font-family: "Trebuchet MS", Arial, sans-serif; font-size: 11pt;'>Hello,</p>
<p style='font-family: "Trebuchet MS", Arial, sans-serif; font-size: 11pt;'>A ticket currently assigned to you with a subject of <i>{{ ticket.title }}</i> has had a resolution added.</p>
<p style='font-family: "Trebuchet MS", Arial, sans-serif; font-size: 11pt;'>The following resolution was added to ticket <b>{{ ticket.ticket }}</b>:</p>
<blockquote style='font-family: "Trebuchet MS", Arial, sans-serif; font-size: 11pt; padding-left: 20px; border-left: solid #ccc 2px;'>{{ resolution }}</blockquote>
<p style='font-family: "Trebuchet MS", Arial, sans-serif; font-size: 11pt;'>This resolution has been e-mailed to the submitter, who will verify it before you can close this ticket.</p>
<p style='font-family: "Trebuchet MS", Arial, sans-serif; font-size: 11pt;'>If you wish to view this ticket online, you can visit <a href="{{ ticket.get_staff_url }}">{{ ticket.get_staff_url }}</a>.</p>
{% endblock %}

View File

@ -1,13 +0,0 @@
Hello,
A ticket currently assigned to you with a subject of {{ ticket.title }} has had a resolution added.
The following resolution was added to ticket {{ ticket.ticket }}:
{{ resolution }}
This resolution has been e-mailed to the submitter, who will verify it before you can close this ticket.
If you wish to view this ticket online, you can visit {{ ticket.get_staff_url }}.
{% include "helpdesk/emails/text_footer.txt" %}

View File

@ -1,18 +0,0 @@
{% extends "helpdesk/emails/base.html" %}
{% block header %}Ticket Updated{% endblock %}
{% block content %}
<p style='font-family: "Trebuchet MS", Arial, sans-serif; font-size: 11pt;'>Hello,</p>
<p style='font-family: "Trebuchet MS", Arial, sans-serif; font-size: 11pt;'>Ticket <a href='{{ ticket.staff_url }}'>{{ ticket.ticket }}</a> (<em>{{ ticket.title }}</em>), which is assigned to you, has been updated.</p>
<p style='font-family: "Trebuchet MS", Arial, sans-serif; font-size: 11pt;'>The following comment was added to ticket <b>{{ ticket.ticket }}</b>:</p>
<blockquote style='font-family: "Trebuchet MS", Arial, sans-serif; font-size: 11pt; padding-left: 20px; border-left: solid #ccc 2px;'>{{ comment }}</blockquote>
<p style='font-family: "Trebuchet MS", Arial, sans-serif; font-size: 11pt;'>This information has {% if private %}not {% endif %} been e-mailed to the submitter.</p>
<p style='font-family: "Trebuchet MS", Arial, sans-serif; font-size: 11pt;'>If you wish to view this ticket online, you can visit <a href="{{ ticket.staff_url }}">{{ ticket.staff_url }}</a>.</p>
{% endblock %}

View File

@ -1,13 +0,0 @@
Hello,
Ticket {{ ticket.ticket }} ({{ ticket.title }}), which is assigned to you, has been updated.
The following comment was added to ticket {{ ticket.ticket }}:
{{ comment }}
This information has {% if private %}not {% endif %} been e-mailed to the submitter.
If you wish to view this ticket online, you can visit {{ ticket.staff_url }}.
{% include "helpdesk/emails/text_footer.txt" %}

View File

@ -1,9 +0,0 @@
{% extends "helpdesk/emails/base.html" %}
{% block header %}E-Mail Heading Here{% endblock %}
{% block content %}
<p style='font-family: "Trebuchet MS", Arial, sans-serif; font-size: 11pt;'>Dear User,</p>
<p style='font-family: "Trebuchet MS", Arial, sans-serif; font-size: 11pt;'>This is some content.</p>
{% endblock %}

View File

@ -1,14 +0,0 @@
{% extends "helpdesk/emails/base.html" %}
{% block header %}Your Ticket Has Been Closed{% endblock %}
{% block content %}
<p style='font-family: "Trebuchet MS", Arial, sans-serif; font-size: 11pt;'>Hello,</p>
<p style='font-family: "Trebuchet MS", Arial, sans-serif; font-size: 11pt;'>You recently logged a ticket with a subject of <i>{{ ticket.title }}</i> with us. This e-mail is to confirm that this ticket has been closed.</p>
<p style='font-family: "Trebuchet MS", Arial, sans-serif; font-size: 11pt;'>If you believe that further work is required on this ticket, please let us know by replying to this e-mail and keeping the subject intact.</p>
<p style='font-family: "Trebuchet MS", Arial, sans-serif; font-size: 11pt;'>If you wish to view this ticket online, you can visit <a href="{{ ticket.get_ticket_url }}">{{ ticket.get_ticket_url }}</a>.</p>
{% endblock %}

View File

@ -1,9 +0,0 @@
Hello,
You recently logged a ticket with a subject of "{{ ticket.title }}" with us. This e-mail is to confirm that this ticket has been closed.
If you believe that further work is required on this ticket, please let us know by replying to this e-mail and keeping the subject intact.
If you wish to view this ticket online, you can visit {{ ticket.get_ticket_url }}.
{% include "helpdesk/emails/text_footer.txt" %}

View File

@ -1,14 +0,0 @@
{% extends "helpdesk/emails/base.html" %}
{% block header %}Your Ticket Has Been Escalated{% endblock %}
{% block content %}
<p style='font-family: "Trebuchet MS", Arial, sans-serif; font-size: 11pt;'>Hello,</p>
<p style='font-family: "Trebuchet MS", Arial, sans-serif; font-size: 11pt;'>You recently logged a ticket with a subject of <i>{{ ticket.title }}</i> with us. This e-mail is to advise you of an automated escalation of that ticket.</p>
<p style='font-family: "Trebuchet MS", Arial, sans-serif; font-size: 11pt;'>We will review your ticket shortly and attempt to provide a resolution as soon as possible.</p>
<p style='font-family: "Trebuchet MS", Arial, sans-serif; font-size: 11pt;'>If you wish to view this ticket online, you can visit <a href="{{ ticket.get_ticket_url }}">{{ ticket.get_ticket_url }}</a>.</p>
{% endblock %}

View File

@ -1,9 +0,0 @@
Hello,</p>
You recently logged a ticket with a subject of "{{ ticket.title }}" with us. This e-mail is to advise you of an automated escalation of that ticket.
We will review your ticket shortly and attempt to provide a resolution as soon as possible.
If you wish to view this ticket online, you can visit {{ ticket.get_ticket_url }}.
{% include "helpdesk/emails/text_footer.txt" %}

View File

@ -1,19 +0,0 @@
{% extends "helpdesk/emails/base.html" %}
{% block header %}Thank You For Your Submission{% endblock %}
{% block content %}
<p style='font-family: "Trebuchet MS", Arial, sans-serif; font-size: 11pt;'>Hello,</p>
<p style='font-family: "Trebuchet MS", Arial, sans-serif; font-size: 11pt;'>This is a courtesy e-mail to let you know that we have received your helpdesk query with a subject of <i>{{ ticket.title }}</i>. </p>
<p style='font-family: "Trebuchet MS", Arial, sans-serif; font-size: 11pt;'>You do not have to do anything further at this stage. Your ticket has been assigned a number of <b>{{ ticket.ticket }}</b>.</p>
<p style='font-family: "Trebuchet MS", Arial, sans-serif; font-size: 11pt;'>If you wish to send us further details, or if you have any queries about this ticket, please include the ticket id of <b>{{ ticket.ticket }}</b> in the subject. The easiest way to do this is just press 'reply' to this message.</p>
<p style='font-family: "Trebuchet MS", Arial, sans-serif; font-size: 11pt;'>If you wish to view this ticket online, you can visit <a href="{{ ticket.get_ticket_url }}">{{ ticket.get_ticket_url }}</a>.</p>
<p style='font-family: "Trebuchet MS", Arial, sans-serif; font-size: 11pt;'>We will investigate your query and attempt to resolve it as soon as possible. You will receive further updates and a resolution via this e-mail address.</p>
{% endblock %}

View File

@ -1,13 +0,0 @@
Hello,
This is a courtesy e-mail to let you know that we have received your helpdesk query with a subject of {{ ticket.title }}.
You do not have to do anything further at this stage. Your ticket has been assigned a number of {{ ticket.ticket }}.
If you wish to send us further details, or if you have any queries about this ticket, please include the ticket id of {{ ticket.ticket }} in the subject. The easiest way to do this is just press 'reply' to this message.
If you wish to view this ticket online, you can visit {{ ticket.get_ticket_url }}.
We will investigate your query and attempt to resolve it as soon as possible. You will receive further updates and a resolution via this e-mail address.
{% include "helpdesk/emails/text_footer.txt" %}

View File

@ -1,18 +0,0 @@
{% extends "helpdesk/emails/base.html" %}
{% block header %}Your Ticket Has Been Resolved{% endblock %}
{% block content %}
<p style='font-family: "Trebuchet MS", Arial, sans-serif; font-size: 11pt;'>Hello,</p>
<p style='font-family: "Trebuchet MS", Arial, sans-serif; font-size: 11pt;'>You recently logged a ticket with a subject of <i>{{ ticket.title }}</i> with us. This e-mail is to advise you of a resolution to that ticket.</p>
<p style='font-family: "Trebuchet MS", Arial, sans-serif; font-size: 11pt;'>The following resolution was added to ticket <b>{{ ticket.ticket }}</b>:</p>
<blockquote style='font-family: "Trebuchet MS", Arial, sans-serif; font-size: 11pt; padding-left: 20px; border-left: solid #ccc 2px;'>{{ resolution }}</blockquote>
<p style='font-family: "Trebuchet MS", Arial, sans-serif; font-size: 11pt;'>Can you please confirm that this resolution addresses your needs so we may close this ticket? If you have any further queries, or if you do not believe this resolution is adequate, please reply to this e-mail and keep the subject intact.</p>
<p style='font-family: "Trebuchet MS", Arial, sans-serif; font-size: 11pt;'>If you wish to view this ticket online, you can visit <a href="{{ ticket.get_ticket_url }}">{{ ticket.get_ticket_url }}</a>.</p>
{% endblock %}

View File

@ -1,13 +0,0 @@
Hello,
You recently logged a ticket with a subject of '{{ ticket.title }}' with us. This e-mail is to advise you of a resolution to that ticket.
The following resolution was added to ticket {{ ticket.ticket }}:
{{ resolution }}
Can you please confirm that this resolution addresses your needs so we may close this ticket? If you have any further queries, or if you do not believe this resolution is adequate, please reply to this e-mail and keep the subject intact.
If you wish to view this ticket online, you can visit {{ ticket.get_ticket_url }}.
{% include "helpdesk/emails/text_footer.txt" %}

View File

@ -1,18 +0,0 @@
{% extends "helpdesk/emails/base.html" %}
{% block header %}Your Ticket Has Been Updated{% endblock %}
{% block content %}
<p style='font-family: "Trebuchet MS", Arial, sans-serif; font-size: 11pt;'>Hello,</p>
<p style='font-family: "Trebuchet MS", Arial, sans-serif; font-size: 11pt;'>You recently logged a ticket with a subject of <i>{{ ticket.title }}</i> with us. This e-mail is to advise you of an update to that ticket.</p>
<p style='font-family: "Trebuchet MS", Arial, sans-serif; font-size: 11pt;'>The following comment was added to ticket <b>{{ ticket.ticket }}</b>:</p>
<blockquote style='font-family: "Trebuchet MS", Arial, sans-serif; font-size: 11pt; padding-left: 20px; border-left: solid #ccc 2px;'>{{ comment }}</blockquote>
<p style='font-family: "Trebuchet MS", Arial, sans-serif; font-size: 11pt;'>To provide us with further information, please reply to this e-mail.</p>
<p style='font-family: "Trebuchet MS", Arial, sans-serif; font-size: 11pt;'>If you wish to view this ticket online, you can visit <a href="{{ ticket.get_ticket_url }}">{{ ticket.get_ticket_url }}</a>.</p>
{% endblock %}

View File

@ -1,13 +0,0 @@
Hello,
You recently logged a ticket with a subject of '{{ ticket.title }}' with us. This e-mail is to advise you of an update to that ticket.
The following comment was added to ticket {{ ticket.ticket }}:
{{ comment }}
To provide us with further information, please reply to this e-mail.
If you wish to view this ticket online, you can visit {{ ticket.get_ticket_url }}.
{% include "helpdesk/emails/text_footer.txt" %}

View File

@ -0,0 +1,27 @@
{% extends "helpdesk/base.html" %}
{% block helpdesk_title %}Reports &amp; Statistics{% endblock %}
{% block helpdesk_body %}
<h2>Reports &amp; Statistics</h2>
<ul>
<li>User<ul>
<li><a href='userpriority/'>by Priority</a></li>
<li><a href='userqueue/'>by Queue</a></li>
<li><a href='userstatus/'>by Status</a></li>
<li><a href='usermonth/'>by Month</a></li>
</ul></li>
<li>Queue<ul>
<li><a href='queuepriority/'>by Priority</a></li>
<li><a href='queuestatus/'>by Status</a></li>
<li><a href='queuemonth/'>by Month</a></li>
</ul></li>
</ul>
{% endblock %}

View File

@ -0,0 +1,15 @@
{% extends "helpdesk/base.html" %}
{% block helpdesk_title %}Reports &amp; Statistics{% endblock %}
{% block helpdesk_body %}
<h2>Reports &amp; Statistics</h2>
<table>
<tr>{% for h in headings %}<th>{{ h }}</th>{% endfor %}</tr>
{% for d in data %}<tr>{% for f in d %}<td>{{ f }}</td>{% endfor %}</tr>{% endfor %}
</table>
{% if chart %}<img src='{{ chart }}' />{% endif %}
{% endblock %}

View File

@ -59,6 +59,14 @@ urlpatterns = patterns('helpdesk.views',
url(r'^rss/$', url(r'^rss/$',
'rss_list', 'rss_list',
name='helpdesk_rss_index'), name='helpdesk_rss_index'),
url(r'^reports/$',
'report_index',
name='helpdesk_report_index'),
url(r'^reports/(?P<report>\w+)/$',
'run_report',
name='helpdesk_run_report'),
) )
urlpatterns += patterns('', urlpatterns += patterns('',
@ -67,6 +75,7 @@ urlpatterns += patterns('',
{'feed_dict': feed_setup}, {'feed_dict': feed_setup},
name='helpdesk_rss'), name='helpdesk_rss'),
) )
urlpatterns += patterns('', urlpatterns += patterns('',
url(r'^api/(?P<method>[a-z_-]+)/$', url(r'^api/(?P<method>[a-z_-]+)/$',
'helpdesk.api.api', 'helpdesk.api.api',

227
views.py
View File

@ -17,7 +17,7 @@ from django.shortcuts import render_to_response, get_object_or_404
from django.template import loader, Context, RequestContext from django.template import loader, Context, RequestContext
from helpdesk.forms import TicketForm, PublicTicketForm from helpdesk.forms import TicketForm, PublicTicketForm
from helpdesk.lib import send_multipart_mail from helpdesk.lib import send_templated_mail, line_chart, bar_chart
from helpdesk.models import Ticket, Queue, FollowUp, TicketChange, PreSetReply, Attachment from helpdesk.models import Ticket, Queue, FollowUp, TicketChange, PreSetReply, Attachment
def dashboard(request): def dashboard(request):
@ -29,15 +29,22 @@ def dashboard(request):
if request.user.is_authenticated(): if request.user.is_authenticated():
tickets = Ticket.objects.filter(assigned_to=request.user).exclude(status=Ticket.CLOSED_STATUS) 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) unassigned_tickets = Ticket.objects.filter(assigned_to__isnull=True).exclude(status=Ticket.CLOSED_STATUS)
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(),
})
from django.db import connection
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 = cursor.dictfetchall()
return render_to_response('helpdesk/dashboard.html', return render_to_response('helpdesk/dashboard.html',
RequestContext(request, { RequestContext(request, {
'user_tickets': tickets, 'user_tickets': tickets,
@ -164,50 +171,38 @@ def update_ticket(request, ticket_id):
'comment': f.comment, 'comment': f.comment,
} }
if f.new_status == Ticket.RESOLVED_STATUS: if f.new_status == Ticket.RESOLVED_STATUS:
template = 'helpdesk/emails/submitter_resolved' template = 'resolved_submitter'
subject = '%s %s (Resolved)' % (ticket.ticket, ticket.title)
elif f.new_status == Ticket.CLOSED_STATUS: elif f.new_status == Ticket.CLOSED_STATUS:
template = 'helpdesk/emails/submitter_closed' template = 'closed_submitter'
subject = '%s %s (Closed)' % (ticket.ticket, ticket.title)
else: else:
template = 'helpdesk/emails/submitter_updated' template = 'updated_submitter'
subject = '%s %s (Updated)' % (ticket.ticket, ticket.title) send_templated_mail(template, context, recipients=ticket.submitter_email, sender=ticket.queue.from_address, fail_silently=True)
send_multipart_mail(template, context, subject, ticket.submitter_email, ticket.queue.from_address, fail_silently=True)
if ticket.assigned_to and request.user != ticket.assigned_to and ticket.assigned_to.email: if ticket.assigned_to and request.user != ticket.assigned_to and ticket.assigned_to.email:
# We only send e-mails to staff members if the ticket is updated by # We only send e-mails to staff members if the ticket is updated by
# another user. # another user.
if reassigned: if reassigned:
template_staff = 'helpdesk/emails/owner_assigned' template_staff = 'assigned_owner'
subject = '%s %s (Assigned)' % (ticket.ticket, ticket.title)
elif f.new_status == Ticket.RESOLVED_STATUS: elif f.new_status == Ticket.RESOLVED_STATUS:
template_staff = 'helpdesk/emails/owner_resolved' template_staff = 'resolved_owner'
subject = '%s %s (Resolved)' % (ticket.ticket, ticket.title)
elif f.new_status == Ticket.CLOSED_STATUS: elif f.new_status == Ticket.CLOSED_STATUS:
template_staff = 'helpdesk/emails/owner_closed' template_staff = 'closed_owner'
subject = '%s %s (Closed)' % (ticket.ticket, ticket.title)
else: else:
template_staff = 'helpdesk/emails/owner_updated' template_staff = 'updated_owner'
subject = '%s %s (Updated)' % (ticket.ticket, ticket.title)
send_multipart_mail(template_staff, context, subject, ticket.assigned_to.email, ticket.queue.from_address, fail_silently=True) send_templated_mail(template_staff, context, recipients=ticket.assigned_to.email, sender=ticket.queue.from_address, fail_silently=True)
if ticket.queue.updated_ticket_cc: if ticket.queue.updated_ticket_cc:
if reassigned: if reassigned:
template_cc = 'helpdesk/emails/cc_assigned' template_cc = 'assigned_cc'
subject = '%s %s (Assigned)' % (ticket.ticket, ticket.title) elif f.new_status == Ticket.RESOLVED_STATUS:
elif f.new_status == Ticket.RESOLVED_STATUS: template_cc = 'resolved_cc'
template_cc = 'helpdesk/emails/cc_resolved' elif f.new_status == Ticket.CLOSED_STATUS:
subject = '%s %s (Resolved)' % (ticket.ticket, ticket.title) template_cc = 'closed_cc'
elif f.new_status == Ticket.CLOSED_STATUS: else:
template_cc = 'helpdesk/emails/cc_closed' template_cc = 'updated_cc'
subject = '%s %s (Closed)' % (ticket.ticket, ticket.title)
else: send_templated_mail(template_cc, context, recipients=ticket.queue.updated_ticket_cc, sender=ticket.queue.from_address, fail_silently=True)
template_cc = 'helpdesk/emails/cc_updated'
subject = '%s %s (Updated)' % (ticket.ticket, ticket.title)
send_multipart_mail(template_cc, context, subject, ticket.queue.updated_ticket_cc, ticket.queue.from_address, fail_silently=True)
if request.FILES: if request.FILES:
for file in request.FILES.getlist('attachment'): for file in request.FILES.getlist('attachment'):
@ -363,3 +358,153 @@ def rss_list(request):
'queues': Queue.objects.all(), 'queues': Queue.objects.all(),
})) }))
rss_list = login_required(rss_list) rss_list = login_required(rss_list)
def report_index(request):
return render_to_response('helpdesk/report_index.html',
RequestContext(request, {}))
report_index = login_required(report_index)
def run_report(request, report):
priority_sql = []
priority_columns = []
for p in Ticket.PRIORITY_CHOICES:
priority_sql.append("COUNT(CASE t.priority WHEN '%s' THEN t.id END) AS \"%s\"" % (p[0], p[1]))
priority_columns.append("%s" % p[1])
priority_sql = ", ".join(priority_sql)
status_sql = []
status_columns = []
for s in Ticket.STATUS_CHOICES:
status_sql.append("COUNT(CASE t.status WHEN '%s' THEN t.id END) AS \"%s\"" % (s[0], s[1]))
status_columns.append("%s" % s[1])
status_sql = ", ".join(status_sql)
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 = []
first_ticket = Ticket.objects.all().order_by('created')[0]
first_month = first_ticket.created.month
first_year = first_ticket.created.year
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:
periods.append((year, month))
month += 1
if month > 12:
year += 1
month = 1
if month > last_month and year >= last_year:
working = False
for (year, month) in periods:
sqlmonth = '%s-%s' % (year, months[month-1])
desc = '%s %s' % (months[month-1], year)
month_sql.append("COUNT(CASE to_char(t.created, 'YYYY-Mon') WHEN '%s' THEN t.id END) AS \"%s\"" % (sqlmonth, desc))
month_columns.append(desc)
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
elif report == 'userqueue':
sql = user_base_sql % queue_sql
columns = ['username'] + queue_columns
elif report == 'userstatus':
sql = user_base_sql % status_sql
columns = ['username'] + status_columns
elif report == 'usermonth':
sql = user_base_sql % month_sql
columns = ['username'] + month_columns
elif report == 'queuepriority':
sql = queue_base_sql % priority_sql
columns = ['queue'] + priority_columns
elif report == 'queuestatus':
sql = queue_base_sql % status_sql
columns = ['queue'] + status_columns
elif report == 'queuemonth':
sql = queue_base_sql % month_sql
columns = ['queue'] + month_columns
from django.db import connection
cursor = connection.cursor()
cursor.execute(sql)
report_output = cursor.dictfetchall()
data = []
for record in report_output:
line = []
for c in columns:
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 = ''
return render_to_response('helpdesk/report_output.html',
RequestContext(request, {
'headings': columns,
'data': data,
'sql': sql,
'chart': chart_url,
}))
run_report = login_required(run_report)