forked from extern/django-helpdesk
Initial import of Python files & templates
This commit is contained in:
commit
015a7dd166
0
__init__.py
Normal file
0
__init__.py
Normal file
67
forms.py
Normal file
67
forms.py
Normal file
@ -0,0 +1,67 @@
|
||||
""" ..
|
||||
.,::;::::::
|
||||
..,::::::::,,,,::: 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$
|
||||
|
||||
"""
|
||||
|
||||
from django import newforms as forms
|
||||
from helpdesk.models import Ticket, Queue
|
||||
from datetime import datetime
|
||||
|
||||
class TicketForm(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 the problem')
|
||||
|
||||
submitter_email = forms.EmailField(required=False,
|
||||
label=u'Submitter E-Mail Address')
|
||||
|
||||
body = forms.CharField(widget=forms.Textarea(),
|
||||
label=u'Description of Issue', required=True)
|
||||
|
||||
assigned_to = forms.ChoiceField(choices=(), required=False,
|
||||
label=u'Case owner')
|
||||
|
||||
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'],
|
||||
)
|
||||
if self.cleaned_data['assigned_to']:
|
||||
t.assigned_to = self.cleaned_data['assigned_to']
|
||||
t.save()
|
||||
|
||||
return t
|
139
models.py
Normal file
139
models.py
Normal file
@ -0,0 +1,139 @@
|
||||
""" ..
|
||||
.,::;::::::
|
||||
..,::::::::,,,,::: 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$
|
||||
|
||||
"""
|
||||
from django.db import models
|
||||
from datetime import datetime
|
||||
from django.contrib.auth.models import User
|
||||
from django.db.models import permalink
|
||||
|
||||
class Queue(models.Model):
|
||||
title = models.CharField(maxlength=100)
|
||||
slug = models.SlugField()
|
||||
email_address = models.EmailField(blank=True, null=True)
|
||||
|
||||
def __unicode__(self):
|
||||
return u"%s" % self.title
|
||||
|
||||
class Admin:
|
||||
pass
|
||||
|
||||
|
||||
class Ticket(models.Model):
|
||||
"""
|
||||
To allow a ticket to be entered as quickly as possible, only the
|
||||
bare minimum fields are required. These basically allow us to
|
||||
sort and manage the ticket. The user can always go back and
|
||||
enter more information later.
|
||||
|
||||
A good example of this is when a customer is on the phone, and
|
||||
you want to give them a ticket ID as quickly as possible. You can
|
||||
enter some basic info, save the ticket, give the customer the ID
|
||||
and get off the phone, then add in further detail at a later time
|
||||
(once the customer is not on the line).
|
||||
"""
|
||||
|
||||
OPEN_STATUS = 1
|
||||
REOPENED_STATUS = 2
|
||||
RESOLVED_STATUS = 3
|
||||
CLOSED_STATUS = 4
|
||||
|
||||
STATUS_CHOICES = (
|
||||
(OPEN_STATUS, 'Open'),
|
||||
(REOPENED_STATUS, 'Reopened'),
|
||||
(RESOLVED_STATUS, 'Resolved'),
|
||||
(CLOSED_STATUS, 'Closed'),
|
||||
)
|
||||
|
||||
title = models.CharField(maxlength=200)
|
||||
queue = models.ForeignKey(Queue)
|
||||
created = models.DateTimeField(auto_now_add=True)
|
||||
submitter_email = models.EmailField(blank=True, null=True, help_text='The submitter will receive an email for all public follow-ups left for this task.')
|
||||
assigned_to = models.ForeignKey(User, related_name='assigned_to', blank=True, null=True)
|
||||
status = models.IntegerField(choices=STATUS_CHOICES, default=OPEN_STATUS)
|
||||
|
||||
description = models.TextField(blank=True, null=True)
|
||||
resolution = models.TextField(blank=True, null=True)
|
||||
|
||||
def _get_assigned_to(self):
|
||||
if not self.assigned_to:
|
||||
return 'Unassigned'
|
||||
else:
|
||||
if self.assigned_to.get_full_name():
|
||||
return self.assigned_to.get_full_name()
|
||||
else:
|
||||
return self.assigned_to
|
||||
get_assigned_to = property(_get_assigned_to)
|
||||
|
||||
class Admin:
|
||||
list_display = ('title', 'status', 'assigned_to',)
|
||||
date_hierarchy = 'created'
|
||||
list_filter = ('assigned_to',)
|
||||
search_fields = ('title',)
|
||||
|
||||
class Meta:
|
||||
get_latest_by = "created"
|
||||
|
||||
def __unicode__(self):
|
||||
return '%s' % self.title
|
||||
|
||||
def get_absolute_url(self):
|
||||
return ('helpdesk.views.view_ticket', [str(self.id)])
|
||||
get_absolute_url = permalink(get_absolute_url)
|
||||
|
||||
def save(self):
|
||||
if not self.id:
|
||||
# This is a new ticket as no ID yet exists.
|
||||
self.created = datetime.now()
|
||||
|
||||
super(Ticket, self).save()
|
||||
|
||||
|
||||
class FollowUp(models.Model):
|
||||
ticket = models.ForeignKey(Ticket)
|
||||
date = models.DateTimeField(auto_now_add=True)
|
||||
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)
|
||||
|
||||
new_status = models.IntegerField(choices=Ticket.STATUS_CHOICES, blank=True, null=True)
|
||||
|
||||
class Meta:
|
||||
ordering = ['date']
|
||||
|
||||
class Admin:
|
||||
pass
|
||||
|
||||
def __unicode__(self):
|
||||
return '%s' % self.title
|
||||
|
||||
class TicketChange(models.Model):
|
||||
followup = models.ForeignKey(FollowUp, edit_inline=models.TABULAR)
|
||||
field = models.CharField(maxlength=100, core=True)
|
||||
old_value = models.TextField(blank=True, null=True, core=True)
|
||||
new_value = models.TextField(blank=True, null=True, core=True)
|
27
templates/helpdesk/base.html
Normal file
27
templates/helpdesk/base.html
Normal file
@ -0,0 +1,27 @@
|
||||
<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 %}'>Dashboard</a></li>
|
||||
<li><a href='{% url helpdesk_list %}'>Tickets</a></li>
|
||||
<li><a href='{% url helpdesk_submit %}'>Submit Ticket</a></li>
|
||||
<li>Search</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>
|
42
templates/helpdesk/create_ticket.html
Normal file
42
templates/helpdesk/create_ticket.html
Normal file
@ -0,0 +1,42 @@
|
||||
{% extends "helpdesk/base.html" %}
|
||||
{% block helpdesk_title %}Helpdesk{% endblock %}
|
||||
|
||||
{% block helpdesk_body %}
|
||||
<h2>Submit a Ticket</h2>
|
||||
|
||||
<form method='post' action='./'>
|
||||
<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 %}
|
||||
|
||||
<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_assigned_to'>{{ form.assigned_to.label }}</label></dt>
|
||||
<dd>{{ form.assigned_to }}</dd>
|
||||
{% if form.assigned_to.errors %}
|
||||
<dd class='error'>{{ form.assigned_to.errors }}</dd>{% endif %}
|
||||
</dl>
|
||||
|
||||
<div class='buttons'>
|
||||
<input type='submit' value='Submit' />
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
</form>
|
||||
{% endblock %}
|
49
templates/helpdesk/dashboard.html
Normal file
49
templates/helpdesk/dashboard.html
Normal file
@ -0,0 +1,49 @@
|
||||
{% extends "helpdesk/base.html" %}
|
||||
{% block helpdesk_title %}Helpdesk Dashboard{% endblock %}
|
||||
{% block helpdesk_head %}<script type='text/javascript' language='javascript'>
|
||||
$(document).ready(function() {
|
||||
$("tr.row_hover").mouseover(function() {$(this).addClass("hover");}).mouseout(function() {$(this).removeClass("hover");});
|
||||
});
|
||||
</script>{% endblock %}
|
||||
{% block helpdesk_body %}
|
||||
<table width='30%'>
|
||||
<tr class='row_tablehead'><td colspan='4'>Helpdesk Summary</td></tr>
|
||||
<tr class='row_columnheads'><th>Queue</th><th>Open</th><th>Resolved</th></tr>
|
||||
{% for queue in dash_tickets %}
|
||||
<tr class='row_{% cycle odd,even %} row_hover '>
|
||||
<th><a href='{% url helpdesk_list %}?queue={{ queue.queue.id }}'>{{ queue.queue }}</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.resolved %}<a href='{% url helpdesk_list %}?queue={{ queue.queue.id }}&status=3'>{% endif %}{{ queue.resolved }}{% if queue.resolved %}</a>{% endif %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
<table width='100%'>
|
||||
<tr class='row_tablehead'><td colspan='5'>Your Tickets</td></tr>
|
||||
<tr class='row_columnheads'><th>#</th><th>Title</th><th>Queue</th><th>Status</th><th>Last Update</th></tr>
|
||||
{% for ticket in user_tickets %}
|
||||
<tr class='row_{% cycle odd,even %} row_hover'>
|
||||
<th><a href='{{ ticket.get_absolute_url }}'>{{ ticket.id }}</a></th>
|
||||
<th><a href='{{ ticket.get_absolute_url }}'>{{ ticket.title }}</a></th>
|
||||
<td>{{ ticket.queue }}</td>
|
||||
<td>{{ ticket.get_status_display }}</td>
|
||||
<td>{{ ticket.last_update|timesince }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
<table width='100%'>
|
||||
<tr class='row_tablehead'><td colspan='5'>Unassigned Tickets</td></tr>
|
||||
<tr class='row_columnheads'><th>#</th><th>Title</th><th>Queue</th><th>Created</th><th> </th></tr>
|
||||
{% for ticket in unassigned_tickets %}
|
||||
<tr class='row_{% cycle odd,even %} row_hover'>
|
||||
<th><a href='{{ ticket.get_absolute_url }}'>{{ ticket.id }}</a></th>
|
||||
<th><a href='{{ ticket.get_absolute_url }}'>{{ ticket.title }}</a></th>
|
||||
<td>{{ ticket.queue }}</td>
|
||||
<td>{{ ticket.created|timesince }} ago</td>
|
||||
<th><a href='{{ ticket.get_absolute_url }}?take'>Take</a></th>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
{% endblock %}
|
30
templates/helpdesk/debug.html
Normal file
30
templates/helpdesk/debug.html
Normal file
@ -0,0 +1,30 @@
|
||||
{% if debug %}
|
||||
<div id="debug" style='clear: both;padding-top: 200px'>
|
||||
<h2>Queries</h2>
|
||||
<p>
|
||||
{{ sql_queries|length }} Quer{{ sql_queries|pluralize:"y,ies" }}
|
||||
{% ifnotequal sql_queries|length 0 %}
|
||||
(<span style="cursor: pointer;" onclick="var s=document.getElementById('debugQueryTable').style;s.display=s.display=='none'?'':'none';this.innerHTML=this.innerHTML=='Show'?'Hide':'Show';">Show</span>)
|
||||
{% endifnotequal %}
|
||||
</p>
|
||||
<table id="debugQueryTable" style="display: none;">
|
||||
<col width="1"></col>
|
||||
<col></col>
|
||||
<col width="1"></col>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">#</th>
|
||||
<th scope="col">SQL</th>
|
||||
<th scope="col">Time</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for query in sql_queries %}<tr class="{% cycle odd,even %}">
|
||||
<td>{{ forloop.counter }}</td>
|
||||
<td>{{ query.sql|escape }}</td>
|
||||
<td>{{ query.time }}</td>
|
||||
</tr>{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endif %}
|
101
templates/helpdesk/ticket.html
Normal file
101
templates/helpdesk/ticket.html
Normal file
@ -0,0 +1,101 @@
|
||||
|
||||
{% extends "helpdesk/base.html" %}
|
||||
{% block helpdesk_title %}Helpdesk{% endblock %}
|
||||
{% block helpdesk_head %}
|
||||
<script src="http://media.jutda.com.au/helpdesk/nicEdit.js" type="text/javascript"></script>
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function() {
|
||||
new nicEditor({buttonList: ['bold','italic','underline','strikeThrough','undo','redo','subscript','superscript','html']}).panelInstance('commentBox');
|
||||
$("#ShowFurtherEditOptions").click(function() {
|
||||
$("#FurtherEditOptions").toggle();
|
||||
return false;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<style type='text/css'>#commentBox { width: 100%; }</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block helpdesk_body %}
|
||||
|
||||
<table width='100%'>
|
||||
<tr class='row_tablehead'><td colspan='2'>{{ ticket.id }}. {{ ticket.title }} [{{ ticket.get_status_display }}]</td>
|
||||
<tr class='row_columnheads'><td colspan='2'>Queue: {{ ticket.queue }}</td></tr>
|
||||
|
||||
<tr class='row_odd'>
|
||||
<th>Submitted On</th>
|
||||
<td>{{ ticket.created }} ({{ ticket.created|timesince }} ago)</td>
|
||||
</tr>
|
||||
|
||||
<tr class='row_even'>
|
||||
<th>Assigned To</th>
|
||||
<td>{{ ticket.get_assigned_to }}</td>
|
||||
</tr>
|
||||
|
||||
{% if ticket.submitter_email %}<tr class='row_odd'>
|
||||
<th>Submitter E-Mail</th>
|
||||
<td>{{ ticket.submitter_email }}</td>
|
||||
</tr>{% endif %}
|
||||
|
||||
</table>
|
||||
|
||||
{% if ticket.followup_set.all %}
|
||||
<h3>Follow-Ups</h3>
|
||||
{% load ticket_to_link %}
|
||||
{% for followup in ticket.followup_set.all %}
|
||||
<div class='followup'>
|
||||
<div class='title'>{{ followup.title }} <span class='byline'>by {{ followup.user }} <span title='at {{ followup.date|date:"r" }}'>{{ followup.date|timesince }} ago</span>{% if not followup.public %} <span class='private'>(Private)</span>{% endif %}</span></div>
|
||||
<p>{{ followup.comment|num_to_link }}</p>
|
||||
{% 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 %}
|
||||
|
||||
<form method='post' action='update/'>
|
||||
<h3>Add a comment</h3>
|
||||
<textarea rows='8' cols='70' name='comment' id='commentBox'></textarea>
|
||||
<p>New Status
|
||||
{% ifequal ticket.status 1 %}
|
||||
<input type='radio' name='new_status' value='1' id='st_open' checked='checked'><label for='st_open' class='active'>Open</label> »
|
||||
<input type='radio' name='new_status' value='3' id='st_resolved'><label for='st_resolved'>Resolved</label> »
|
||||
<input type='radio' name='new_status' value='4' id='st_closed'><label for='st_closed'>Closed</label>
|
||||
{% endifequal %}
|
||||
{% ifequal ticket.status 2 %}
|
||||
<input type='radio' name='new_status' value='2' id='st_reopened' checked='checked'><label for='st_reopened' class='active'>Reopened</label>
|
||||
<input type='radio' name='new_status' value='3' id='st_resolved'><label for='st_resolved'>Resolved</label>
|
||||
<input type='radio' name='new_status' value='4' id='st_closed'><label for='st_closed'>Closed</label>
|
||||
{% endifequal %}
|
||||
{% ifequal ticket.status 3 %}
|
||||
<input type='radio' name='new_status' value='2' id='st_reopened'><label for='st_reopened'>Reopened</label>
|
||||
<input type='radio' name='new_status' value='3' id='st_resolved' checked='checked'><label for='st_resolved' class='active'>Resolved</label>
|
||||
<input type='radio' name='new_status' value='4' id='st_closed'><label for='st_closed'>Closed</label>
|
||||
{% endifequal %}
|
||||
|
||||
{% ifequal ticket.status 4 %}
|
||||
<input type='radio' name='new_status' value='2' id='st_reopened'><label for='st_reopened'>Reopened</label>
|
||||
<input type='radio' name='new_status' value='4' id='st_closed' checked='checked'><label for='st_closed'>Closed</label>
|
||||
{% endifequal %}
|
||||
|
||||
<p><a href='#' id='ShowFurtherEditOptions'>Change Other Details</a></p>
|
||||
|
||||
<div id='FurtherEditOptions' style='display: none;'>
|
||||
|
||||
<label for='id_title'>Title</label>
|
||||
<input type='text' name='title' value='{{ ticket.title|escape }}' />
|
||||
|
||||
<label for='id_owner'>Owner</label>
|
||||
<select id='id_owner' name='owner'><option value='0'>Unassign</option>{% for u in active_users %}<option value='{{ u.id }}' {% ifequal u.id ticket.assigned_to.id %}selected{% endifequal %}>{{ u }}</option>{% endfor %}</select>
|
||||
|
||||
</div>
|
||||
|
||||
<label for='id_public'>Is this update public?</label> <input type='checkbox' name='public' value='1' checked='checked' />
|
||||
|
||||
<input type='submit' value='Add' />
|
||||
|
||||
</form>
|
||||
|
||||
|
||||
{% endblock %}
|
47
templates/helpdesk/ticket_list.html
Normal file
47
templates/helpdesk/ticket_list.html
Normal file
@ -0,0 +1,47 @@
|
||||
{% extends "helpdesk/base.html" %}
|
||||
{% block helpdesk_title %}Ticket Listing{% endblock %}
|
||||
{% block helpdesk_head %}<script type='text/javascript' language='javascript'>
|
||||
$(document).ready(function() {
|
||||
$("tr.row_hover").mouseover(function() {$(this).addClass("hover");}).mouseout(function() {$(this).removeClass("hover");});
|
||||
});
|
||||
</script>{% endblock %}
|
||||
{% block helpdesk_body %}
|
||||
|
||||
{% load in_list %}
|
||||
|
||||
<form method='get' action='./'>
|
||||
<label for='id_sort'>Sorting</label> <select id='id_sort' name='sort'>
|
||||
<option value='created'{% ifequal sort "created"%} selected='selected'{% endifequal %}>Created</option>
|
||||
<option value='title'{% ifequal sort "title"%} selected='selected'{% endifequal %}>Title</option>
|
||||
<option value='queue'{% ifequal sort "queue"%} selected='selected'{% endifequal %}>Queue</option>
|
||||
<option value='status'{% ifequal sort "status"%} selected='selected'{% endifequal %}>Status</option>
|
||||
<option value='assigned_to'{% ifequal sort "assigned_to"%} selected='selected'{% endifequal %}>Owner</option>
|
||||
</select>
|
||||
|
||||
<label for='id_owners'>Owner(s)</label> <select id='id_owners' name='assigned_to' multiple='selected' size='5'>{% for u in user_choices %}<option value='{{ u.id }}'{% if u.id|in_list:owners %} selected='selected'{% endif %}>{{ u.username }}</option>{% endfor %}</select>
|
||||
|
||||
<label for='id_queues'>Queue(s)</label> <select id='id_queues' name='queue' multiple='selected' size='5'>{% for q in queue_choices %}<option value='{{ q.id }}'{% if q.id|in_list:queues %} selected='selected'{% endif %}>{{ q.title }}</option>{% endfor %}</select>
|
||||
|
||||
<label for='id_statuses'>Status(es)</label> {% for s in status_choices %}<input type='checkbox' name='status' value='{{ s.0 }}'{% if s.0|in_list:statuses %} checked='checked'{% endif %}> {{ s.1 }}{% endfor %}
|
||||
|
||||
<input type='submit' value='Go!' />
|
||||
</form>
|
||||
|
||||
<table width='100%'>
|
||||
<tr class='row_tablehead'><td colspan='6'>Tickets</td></tr>
|
||||
<tr class='row_columnheads'><th>#</th><th>Title</th><th>Queue</th><th>Status</th><th>Created</th><th>Owner</th></tr>
|
||||
{% if tickets %}{% for ticket in tickets %}
|
||||
<tr class='row_{% cycle odd,even %} row_hover'>
|
||||
<th><a href='{{ ticket.get_absolute_url }}'>{{ ticket.id }}</a></th>
|
||||
<th><a href='{{ ticket.get_absolute_url }}'>{{ ticket.title }}</a></th>
|
||||
<td>{{ ticket.queue }}</td>
|
||||
<td>{{ ticket.get_status_display }}</td>
|
||||
<td>{{ ticket.created }}</td>
|
||||
<td>{{ ticket.get_assigned_to }}</td>
|
||||
</tr>
|
||||
{% endfor %}{% else %}
|
||||
<tr class='row_odd'><td colspan='5'>No Tickets Match Your Selection</td></tr>
|
||||
{% endif %}
|
||||
</table>
|
||||
|
||||
{% endblock %}
|
20
templates/registration/login.html
Normal file
20
templates/registration/login.html
Normal file
@ -0,0 +1,20 @@
|
||||
{% extends "helpdesk/base.html" %}
|
||||
{% block helpdesk_title %}Helpdesk Login{% endblock %}
|
||||
|
||||
{% block helpdesk_body %}
|
||||
<h2>Login</h2>
|
||||
|
||||
<p>To log in and begin responding to cases, simply enter your username and password below.</p>
|
||||
|
||||
<form method='post' action='./'>
|
||||
{% if form.has_errors %}<p>Your username and password didn't match. Please try again.</p>{% endif %}
|
||||
<dl>
|
||||
<dt><label>Username</label></dt>
|
||||
<dd>{{ form.username }}</dd>
|
||||
<dt><label>Password</label></dt>
|
||||
<dd>{{ form.password }}</dd>
|
||||
</dl>
|
||||
<input type='submit' value='Login' />
|
||||
<input type="hidden" name="next" value="{% if next %}{{ next }}{% else %}../{% endif %}" />
|
||||
</form>
|
||||
{% endblock %}
|
0
templatetags/__init__.py
Normal file
0
templatetags/__init__.py
Normal file
34
templatetags/in_list.py
Normal file
34
templatetags/in_list.py
Normal file
@ -0,0 +1,34 @@
|
||||
""" ..
|
||||
.,::;::::::
|
||||
..,::::::::,,,,::: 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$
|
||||
|
||||
"""
|
||||
|
||||
from django import template
|
||||
def in_list(value, arg):
|
||||
return value in arg
|
||||
register = template.Library()
|
||||
register.filter(in_list)
|
68
templatetags/ticket_to_link.py
Normal file
68
templatetags/ticket_to_link.py
Normal file
@ -0,0 +1,68 @@
|
||||
""" ..
|
||||
.,::;::::::
|
||||
..,::::::::,,,,::: 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$
|
||||
|
||||
"""
|
||||
|
||||
from django import template
|
||||
from helpdesk.models import Ticket
|
||||
|
||||
class ReverseProxy:
|
||||
def __init__(self, sequence):
|
||||
self.sequence = sequence
|
||||
|
||||
def __iter__(self):
|
||||
length = len(self.sequence)
|
||||
i = length
|
||||
while i > 0:
|
||||
i = i - 1
|
||||
yield self.sequence[i]
|
||||
|
||||
def num_to_link(text):
|
||||
import re
|
||||
from django.core.urlresolvers import reverse
|
||||
|
||||
matches = []
|
||||
for match in re.finditer("#(\d+)", text):
|
||||
matches.append(match)
|
||||
|
||||
for match in ReverseProxy(matches):
|
||||
start = match.start()
|
||||
end = match.end()
|
||||
number = match.groups()[0]
|
||||
url = reverse('helpdesk_view', args=[number])
|
||||
try:
|
||||
ticket = Ticket.objects.get(id=number)
|
||||
except:
|
||||
ticket = None
|
||||
|
||||
if ticket:
|
||||
style = ticket.get_status_display()
|
||||
text = "%s<a href='%s' class='ticket_link_status ticket_link_status_%s'>#%s</a>%s" % (text[:match.start()], url, style, match.groups()[0], text[match.end():])
|
||||
return text
|
||||
|
||||
register = template.Library()
|
||||
register.filter(num_to_link)
|
58
urls.py
Normal file
58
urls.py
Normal file
@ -0,0 +1,58 @@
|
||||
""" ..
|
||||
.,::;::::::
|
||||
..,::::::::,,,,::: 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$
|
||||
|
||||
"""
|
||||
|
||||
from django.conf.urls.defaults import *
|
||||
|
||||
urlpatterns = patterns('helpdesk.views',
|
||||
url(r'^$',
|
||||
'dashboard',
|
||||
name='helpdesk_home'),
|
||||
|
||||
url(r'^tickets/$',
|
||||
'ticket_list',
|
||||
name='helpdesk_list'),
|
||||
|
||||
url(r'^tickets/submit/$',
|
||||
'create_ticket',
|
||||
name='helpdesk_submit'),
|
||||
|
||||
url(r'^tickets/(?P<ticket_id>[0-9]+)/$',
|
||||
'view_ticket',
|
||||
name='helpdesk_view'),
|
||||
|
||||
url(r'^tickets/(?P<ticket_id>[0-9]+)/update/$',
|
||||
'update_ticket',
|
||||
name='helpdesk_view'),
|
||||
)
|
||||
|
||||
urlpatterns += patterns('',
|
||||
url(r'^login/$', 'django.contrib.auth.views.login', name='login'),
|
||||
|
||||
url(r'^logout/$', 'django.contrib.auth.views.logout', name='logout'),
|
||||
)
|
185
views.py
Normal file
185
views.py
Normal file
@ -0,0 +1,185 @@
|
||||
""" ..
|
||||
.,::;::::::
|
||||
..,::::::::,,,,::: 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
|
||||
from django.template import loader, Context, RequestContext
|
||||
from django.shortcuts import render_to_response, get_object_or_404
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.db.models import Q
|
||||
|
||||
# Helpdesk imports
|
||||
from helpdesk.forms import TicketForm
|
||||
from helpdesk.models import Ticket, Queue, FollowUp, TicketChange
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
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()
|
||||
|
||||
return render_to_response('helpdesk/ticket.html',
|
||||
RequestContext(request, {
|
||||
'ticket': ticket,
|
||||
'active_users': User.objects.filter(is_active=True),
|
||||
}))
|
||||
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))
|
||||
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
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
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)
|
||||
|
||||
### SORTING
|
||||
sort = request.GET.get('sort', None)
|
||||
if sort not in ('status', 'assigned_to', 'created', 'title', 'queue'):
|
||||
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():
|
||||
ticket = form.save()
|
||||
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)
|
Loading…
Reference in New Issue
Block a user