* Added logout link/template

* Added ability for public to submit a ticket via the web if they aren't logged in
* Added ability for public to view ticket via web using ticket ID & e-mail address
* Added public ticket URL to e-mails
* Added manager to FollowUp class to
This commit is contained in:
Ross Poulton 2008-01-16 00:26:24 +00:00
parent ab320c782b
commit 2e2176547d
18 changed files with 346 additions and 20 deletions

View File

@ -102,3 +102,61 @@ class TicketForm(forms.Form):
send_multipart_mail('helpdesk/emails/submitter_newticket', context, '%s %s' % (t.ticket, t.title), t.submitter_email, q.from_address)
return t
class PublicTicketForm(forms.Form):
queue = forms.ChoiceField(label=u'Queue', required=True, choices=())
title = forms.CharField(max_length=100, required=True,
widget=forms.TextInput(),
label=u'Summary of your query')
submitter_email = forms.EmailField(required=True,
label=u'Your E-Mail Address',
help_text=u'We will e-mail you when your ticket is updated.')
body = forms.CharField(widget=forms.Textarea(),
label=u'Description of your issue', required=True,
help_text=u'Please be as descriptive as possible, including any details we may need to address your query.')
priority = forms.ChoiceField(choices=Ticket.PRIORITY_CHOICES,
required=True,
initial='3',
label=u'Urgency',
help_text=u'Please select a priority carefully.')
def save(self):
"""
Writes and returns a Ticket() object
"""
q = Queue.objects.get(id=int(self.cleaned_data['queue']))
t = Ticket( title = self.cleaned_data['title'],
submitter_email = self.cleaned_data['submitter_email'],
created = datetime.now(),
status = Ticket.OPEN_STATUS,
queue = q,
description = self.cleaned_data['body'],
priority = self.cleaned_data['priority'],
)
t.save()
f = FollowUp( ticket = t,
title = 'Ticket Opened Via Web',
date = datetime.now(),
public = True,
comment = self.cleaned_data['body'],
)
f.save()
context = {
'ticket': t,
'queue': q,
}
from helpdesk.lib import send_multipart_mail
send_multipart_mail('helpdesk/emails/submitter_newticket', context, '%s %s' % (t.ticket, t.title), t.submitter_email, q.from_address)
return t

View File

@ -47,6 +47,11 @@ dd.form_help_text {
font-size: 95%;
}
ul.errorlist {
color: #a33;
font-size: 95%;
}
dt {
padding-top: 8px;
}

View File

@ -145,14 +145,25 @@ class Ticket(models.Model):
""" A user-friendly ticket ID, which is a combination of ticket ID
and queue slug. This is generally used in e-mails. """
return "[%s-%s]" % (self.queue.slug, self.id)
return "[%s]" % (self.ticket_for_url)
ticket = property(_get_ticket)
def _get_ticket_for_url(self):
return "%s-%s" % (self.queue.slug, self.id)
ticket_for_url = property(_get_ticket_for_url)
def _get_priority_img(self):
from django.conf import settings
return "%s/helpdesk/priorities/priority%s.png" % (settings.MEDIA_URL, self.priority)
get_priority_img = property(_get_priority_img)
def _get_ticket_url(self):
from django.contrib.sites.models import Site
from django.core.urlresolvers import reverse
site = Site.objects.get_current()
return "http://%s%s?ticket=%s&email=%s" % (site.domain, reverse('helpdesk_public_view'), self.ticket_for_url, self.submitter_email)
ticket_url = property(_get_ticket_url)
class Admin:
list_display = ('title', 'status', 'assigned_to', 'submitter_email',)
date_hierarchy = 'created'
@ -180,6 +191,12 @@ class Ticket(models.Model):
super(Ticket, self).save()
class FollowUpManager(models.Manager):
def private_followups(self):
return self.filter(public=False)
def public_followups(self):
return self.filter(public=True)
class FollowUp(models.Model):
""" A FollowUp is a comment and/or change to a ticket. We keep a simple
@ -197,10 +214,12 @@ class FollowUp(models.Model):
title = models.CharField(maxlength=200, blank=True, null=True)
comment = models.TextField(blank=True, null=True)
public = models.BooleanField(blank=True, null=True)
user = models.ForeignKey(User)
user = models.ForeignKey(User, blank=True, null=True)
new_status = models.IntegerField(choices=Ticket.STATUS_CHOICES, blank=True, null=True)
objects = FollowUpManager()
class Meta:
ordering = ['date']

View File

@ -13,6 +13,7 @@
<li><a href='{% url helpdesk_home %}'>Dashboard</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 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 %}
</ul>
</div>

View File

@ -12,6 +12,8 @@
<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

@ -6,6 +6,8 @@ You do not have to do anything further at this stage. Your ticket has been assig
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

@ -13,4 +13,6 @@
<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

@ -8,4 +8,6 @@ The following resolution was added to ticket {{ ticket.ticket }}:
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

@ -13,4 +13,6 @@
<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

@ -8,4 +8,6 @@ The following comment was added to ticket {{ ticket.ticket }}:
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,25 @@
<html>
<head>
<title>{% block helpdesk_title %}Helpdesk{% endblock %}</title>
<script src='{{ MEDIA_URL }}/helpdesk/jquery.js' type='text/javascript' language='javascript'></script>
<link rel='stylesheet' href='{{ MEDIA_URL }}/helpdesk/helpdesk.css' type='text/css' />
{% block helpdesk_head %}{% endblock %}
</head>
<body>
<div id='container'>
<div id='header'>
<h1>Helpdesk</h1>
<ul>
<li><a href='{% url helpdesk_home %}'>Submit A Ticket</a></li>
<li><a href='{% url login %}'>Log In</a></li>
</ul>
</div>
<div id='body'>
{% block helpdesk_body %}{% endblock %}
</div>
<div id='footer'>
<p>Powered by <a href='http://www.jutda.com.au/'>Jutda HelpDesk</a></p>
</div>
</div>{% include "helpdesk/debug.html" %}
</body>
</html>

View File

@ -0,0 +1,62 @@
{% extends "helpdesk/public_base.html" %}
{% block helpdesk_title %}Helpdesk{% endblock %}
{% block helpdesk_body %}
<h2>View a Ticket</h2>
<form method='get' action='{% url helpdesk_public_view %}'>
<fieldset>
<dl>
<dt><label for='id_ticket'>Ticket</label></dt>
<dd><input type='text' name='ticket' /></dd>
<dt><label for='id_email'>Your E-mail Address</label></dt>
<dd><input type='text' name='email' /></dd>
</dl>
<input type='submit' value='View Ticket' />
</fieldset>
</form>
<h2 name='submit'>Submit a Ticket</h2>
<p>All fields are required. Please provide as descriptive a title and description as possible.</p>
<form method='post' action='./#submit'>
<fieldset>
<dl>
<dt><label for='id_queue'>{{ form.queue.label }}</label></dt>
<dd>{{ form.queue }}</dd>
{% if form.queue.errors %}
<dd class='error'>{{ form.queue.errors }}</dd>{% endif %}
<dt><label for='id_title'>{{ form.title.label }}</label></dt>
<dd>{{ form.title }}</dd>
{% if form.title.errors %}
<dd class='error'>{{ form.title.errors }}</dd>{% endif %}
<dt><label for='id_submitter_email'>{{ form.submitter_email.label }}</label></dt>
<dd>{{ form.submitter_email }}</dd>
{% if form.submitter_email.errors %}
<dd class='error'>{{ form.submitter_email.errors }}</dd>{% endif %}
<dd class='form_help_text'>{{ form.submitter_email.help_text }}</dd>
<dt><label for='id_body'>{{ form.body.label }}</label></dt>
<dd>{{ form.body }}</dd>
{% if form.body.errors %}
<dd class='error'>{{ form.body.errors }}</dd>{% endif %}
<dt><label for='id_priority'>{{ form.priority.label }}</label></dt>
<dd>{{ form.priority }}</dd>
{% if form.priority.errors %}
<dd class='error'>{{ form.priority.errors }}</dd>{% endif %}
<dd class='form_help_text'>{{ form.priority.help_text }}</dd>
</dl>
<div class='buttons'>
<input type='submit' value='Submit Ticket' />
</div>
</fieldset>
</form>
{% endblock %}

View File

@ -0,0 +1,23 @@
{% extends "helpdesk/public_base.html" %}
{% block helpdesk_title %}Helpdesk{% endblock %}
{% block helpdesk_body %}
<h2>View a Ticket</h2>
<form method='get' action='{% url helpdesk_public_view %}'>
{% if error_message %}<p><strong>Error:</strong> {{ error_message }}</p>{% endif %}
<fieldset>
<dl>
<dt><label for='id_ticket'>Ticket</label></dt>
<dd><input type='text' name='ticket' value='{{ ticket }}' /></dd>
<dt><label for='id_email'>Your E-mail Address</label></dt>
<dd><input type='text' name='email' value='{{ email }}' /></dd>
</dl>
<input type='submit' value='View Ticket' />
</fieldset>
</form>
{% endblock %}

View File

@ -0,0 +1,65 @@
{% extends "helpdesk/public_base.html" %}
{% block helpdesk_title %}Helpdesk{% endblock %}
{% block helpdesk_head %}{% load markup %}
<script src="http://media.jutda.com.au/helpdesk/nicEdit.js" type="text/javascript"></script>
<script type="text/javascript">
$(document).ready(function() {
nic = new nicEditor({buttonList: ['bold','italic','underline','strikeThrough','undo','redo','subscript','superscript','html']}).panelInstance('commentBox');
});
</script>
{% endblock %}
{% block helpdesk_body %}
<table width='100%'>
<tr class='row_tablehead'><td colspan='2'>{{ ticket.id }}. {{ ticket.title }} [{{ ticket.get_status_display }}]</td></tr>
<tr class='row_columnheads'><th colspan='2'>Queue: {{ ticket.queue }}</th></tr>
<tr class='row_odd'>
<th>Submitted On</th>
<td>{{ ticket.created|date:"r" }} ({{ ticket.created|timesince }} ago)</td>
</tr>
<tr class='row_even'>
<th>Submitter E-Mail</th>
<td>{{ ticket.submitter_email }}</td>
</tr>
<tr class='row_odd'>
<th>Priority</th>
<td>{{ ticket.get_priority_display }}</td>
</tr>
<tr class='row_even'>
<th colspan='2'>Description</th>
</tr>
<tr class='row_odd'>
<td colspan='2'>{{ ticket.description }}</td>
</tr>
{% if ticket.resolution %}<tr class='row_even'>
<th colspan='2'>Resolution{% ifequal ticket.get_status_display "Resolved" %} <a href='?close'><img src='{{ MEDIA_URL }}/helpdesk/buttons/accept.png' alt='Accept' title='Accept and Close' width='60' height='15' /></a>{% endifequal %}</th>
</tr>
<tr class='row_odd'>
<td colspan='2'>{{ ticket.resolution }}</td>
</tr>{% endif %}
</table>
{% if ticket.followup_set.public_followups %}
<h3>Follow-Ups</h3>
{% load ticket_to_link %}
{% for followup in ticket.followup_set.public_followups %}
<div class='followup'>
<div class='title'>{{ followup.title }} <span class='byline'>by {{ followup.user }} <span title='{{ followup.date|date:"r" }}'>{{ followup.date|timesince }} ago</span></span></div>
{{ followup.comment|num_to_link }}
{% if followup.ticketchange_set.all %}<div class='changes'>
{% for change in followup.ticketchange_set.all %}
Changed {{ change.field }} from {{ change.old_value }} to {{ change.new_value }}.<br />
{% endfor %}
</div>{% endif %}
</div>
{% endfor %}
{% endif %}
{% endblock %}

View File

@ -1,4 +1,4 @@
{% extends "helpdesk/base.html" %}
{% extends "helpdesk/public_base.html" %}
{% block helpdesk_title %}Helpdesk Login{% endblock %}
{% block helpdesk_body %}

View File

@ -0,0 +1,9 @@
{% extends "helpdesk/public_base.html" %}
{% block helpdesk_title %}Helpdesk{% endblock %}
{% block helpdesk_body %}
<h2>Logout</h2>
<p>Thanks for being here. Hopefully you've helped resolve a few tickets and make the world a better place.</p>
{% endblock %}

View File

@ -58,6 +58,10 @@ urlpatterns = patterns('helpdesk.views',
url(r'^raw/(?P<type>\w+)/$',
'raw_details',
name='helpdesk_raw'),
url(r'^view/$',
'public_view',
name='helpdesk_public_view'),
)
urlpatterns += patterns('',

View File

@ -39,29 +39,50 @@ from django.shortcuts import render_to_response, get_object_or_404
from django.template import loader, Context, RequestContext
# Helpdesk imports
from helpdesk.forms import TicketForm
from helpdesk.forms import TicketForm, PublicTicketForm
from helpdesk.lib import send_multipart_mail
from helpdesk.models import Ticket, Queue, FollowUp, TicketChange, PreSetReply
def dashboard(request):
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)
"""
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)
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(),
})
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,
}))
dashboard = login_required(dashboard)
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,
}))
def delete_ticket(request, ticket_id):
ticket = get_object_or_404(Ticket, id=ticket_id)
@ -260,3 +281,25 @@ def raw_details(request, type):
raise Http404
raw_details = login_required(raw_details)
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,
}))