Initial import of Python files & templates

This commit is contained in:
Ross Poulton 2007-12-27 00:29:17 +00:00
commit 015a7dd166
15 changed files with 867 additions and 0 deletions

0
__init__.py Normal file
View File

67
forms.py Normal file
View 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
View 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)

View 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>

View 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 %}

View 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>&nbsp;</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 %}

View 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 %}

View 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> &raquo;
<input type='radio' name='new_status' value='3' id='st_resolved'><label for='st_resolved'>Resolved</label> &raquo;
<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 %}

View 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 %}

View 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
View File

34
templatetags/in_list.py Normal file
View 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)

View 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
View 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
View 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)