commit 015a7dd1661b450b8e79a16c5a970f9e5b2c1f7e Author: Ross Poulton Date: Thu Dec 27 00:29:17 2007 +0000 Initial import of Python files & templates diff --git a/__init__.py b/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/forms.py b/forms.py new file mode 100644 index 00000000..ecd4070a --- /dev/null +++ b/forms.py @@ -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 diff --git a/models.py b/models.py new file mode 100644 index 00000000..d5d9a994 --- /dev/null +++ b/models.py @@ -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) diff --git a/templates/helpdesk/base.html b/templates/helpdesk/base.html new file mode 100644 index 00000000..ee053c86 --- /dev/null +++ b/templates/helpdesk/base.html @@ -0,0 +1,27 @@ + + +{% block helpdesk_title %}Helpdesk{% endblock %} + + +{% block helpdesk_head %}{% endblock %} + + +
+ +
+{% block helpdesk_body %}{% endblock %} +
+ +
{% include "helpdesk/debug.html" %} + + diff --git a/templates/helpdesk/create_ticket.html b/templates/helpdesk/create_ticket.html new file mode 100644 index 00000000..3ea1c4aa --- /dev/null +++ b/templates/helpdesk/create_ticket.html @@ -0,0 +1,42 @@ +{% extends "helpdesk/base.html" %} +{% block helpdesk_title %}Helpdesk{% endblock %} + +{% block helpdesk_body %} +

Submit a Ticket

+ +
+
+
+
+
{{ form.queue }}
+ {% if form.queue.errors %} +
{{ form.queue.errors }}
{% endif %} + +
+
{{ form.title }}
+ {% if form.title.errors %} +
{{ form.title.errors }}
{% endif %} + +
+
{{ form.submitter_email }}
+ {% if form.submitter_email.errors %} +
{{ form.submitter_email.errors }}
{% endif %} + +
+
{{ form.body }}
+ {% if form.body.errors %} +
{{ form.body.errors }}
{% endif %} + +
+
{{ form.assigned_to }}
+ {% if form.assigned_to.errors %} +
{{ form.assigned_to.errors }}
{% endif %} +
+ +
+ +
+
+ +
+{% endblock %} diff --git a/templates/helpdesk/dashboard.html b/templates/helpdesk/dashboard.html new file mode 100644 index 00000000..524540c6 --- /dev/null +++ b/templates/helpdesk/dashboard.html @@ -0,0 +1,49 @@ +{% extends "helpdesk/base.html" %} +{% block helpdesk_title %}Helpdesk Dashboard{% endblock %} +{% block helpdesk_head %}{% endblock %} +{% block helpdesk_body %} + + + +{% for queue in dash_tickets %} + + + + + +{% endfor %} +
Helpdesk Summary
QueueOpenResolved
{{ queue.queue }}{% if queue.open %}{% endif %}{{ queue.open }}{% if queue.open %}{% endif %}{% if queue.resolved %}{% endif %}{{ queue.resolved }}{% if queue.resolved %}{% endif %}
+ + + + +{% for ticket in user_tickets %} + + + + + + + +{% endfor %} +
Your Tickets
#TitleQueueStatusLast Update
{{ ticket.id }}{{ ticket.title }}{{ ticket.queue }}{{ ticket.get_status_display }}{{ ticket.last_update|timesince }}
+ + + + +{% for ticket in unassigned_tickets %} + + + + + + + +{% endfor %} +
Unassigned Tickets
#TitleQueueCreated 
{{ ticket.id }}{{ ticket.title }}{{ ticket.queue }}{{ ticket.created|timesince }} agoTake
+ +{% endblock %} diff --git a/templates/helpdesk/debug.html b/templates/helpdesk/debug.html new file mode 100644 index 00000000..82b39296 --- /dev/null +++ b/templates/helpdesk/debug.html @@ -0,0 +1,30 @@ +{% if debug %} +
+

Queries

+

+ {{ sql_queries|length }} Quer{{ sql_queries|pluralize:"y,ies" }} + {% ifnotequal sql_queries|length 0 %} + (Show) + {% endifnotequal %} +

+ + + + + + + + + + + + + {% for query in sql_queries %} + + + + {% endfor %} + + +
+{% endif %} diff --git a/templates/helpdesk/ticket.html b/templates/helpdesk/ticket.html new file mode 100644 index 00000000..88b4790f --- /dev/null +++ b/templates/helpdesk/ticket.html @@ -0,0 +1,101 @@ + +{% extends "helpdesk/base.html" %} +{% block helpdesk_title %}Helpdesk{% endblock %} +{% block helpdesk_head %} + + + +{% endblock %} + +{% block helpdesk_body %} + + + + + + + + + + + + + + + +{% if ticket.submitter_email %} + + +{% endif %} + +
{{ ticket.id }}. {{ ticket.title }} [{{ ticket.get_status_display }}]
Queue: {{ ticket.queue }}
Submitted On{{ ticket.created }} ({{ ticket.created|timesince }} ago)
Assigned To{{ ticket.get_assigned_to }}
Submitter E-Mail{{ ticket.submitter_email }}
+ +{% if ticket.followup_set.all %} +

Follow-Ups

+{% load ticket_to_link %} +{% for followup in ticket.followup_set.all %} +
+
{{ followup.title }}
+

{{ followup.comment|num_to_link }}

+{% if followup.ticketchange_set.all %}
+{% for change in followup.ticketchange_set.all %} +Changed {{ change.field }} from {{ change.old_value }} to {{ change.new_value }}.
+{% endfor %} +
{% endif %} +
+{% endfor %} +{% endif %} + +
+

Add a comment

+ +

New Status +{% ifequal ticket.status 1 %} + » + » + +{% endifequal %} +{% ifequal ticket.status 2 %} + + + +{% endifequal %} +{% ifequal ticket.status 3 %} + + + +{% endifequal %} + +{% ifequal ticket.status 4 %} + + +{% endifequal %} + +

Change Other Details

+ + + + + + + +
+ + +{% endblock %} diff --git a/templates/helpdesk/ticket_list.html b/templates/helpdesk/ticket_list.html new file mode 100644 index 00000000..67054416 --- /dev/null +++ b/templates/helpdesk/ticket_list.html @@ -0,0 +1,47 @@ +{% extends "helpdesk/base.html" %} +{% block helpdesk_title %}Ticket Listing{% endblock %} +{% block helpdesk_head %}{% endblock %} +{% block helpdesk_body %} + +{% load in_list %} + +
+ + + + + + + {% for s in status_choices %} {{ s.1 }}{% endfor %} + + +
+ + + + +{% if tickets %}{% for ticket in tickets %} + + + + + + + + +{% endfor %}{% else %} + +{% endif %} +
Tickets
#TitleQueueStatusCreatedOwner
{{ ticket.id }}{{ ticket.title }}{{ ticket.queue }}{{ ticket.get_status_display }}{{ ticket.created }}{{ ticket.get_assigned_to }}
No Tickets Match Your Selection
+ +{% endblock %} diff --git a/templates/registration/login.html b/templates/registration/login.html new file mode 100644 index 00000000..80b4a36f --- /dev/null +++ b/templates/registration/login.html @@ -0,0 +1,20 @@ +{% extends "helpdesk/base.html" %} +{% block helpdesk_title %}Helpdesk Login{% endblock %} + +{% block helpdesk_body %} +

Login

+ +

To log in and begin responding to cases, simply enter your username and password below.

+ +
+ {% if form.has_errors %}

Your username and password didn't match. Please try again.

{% endif %} +
+
+
{{ form.username }}
+
+
{{ form.password }}
+
+ + +
+{% endblock %} diff --git a/templatetags/__init__.py b/templatetags/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/templatetags/in_list.py b/templatetags/in_list.py new file mode 100644 index 00000000..e234e12a --- /dev/null +++ b/templatetags/in_list.py @@ -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) diff --git a/templatetags/ticket_to_link.py b/templatetags/ticket_to_link.py new file mode 100644 index 00000000..339f016b --- /dev/null +++ b/templatetags/ticket_to_link.py @@ -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#%s%s" % (text[:match.start()], url, style, match.groups()[0], text[match.end():]) + return text + +register = template.Library() +register.filter(num_to_link) diff --git a/urls.py b/urls.py new file mode 100644 index 00000000..f55ce3da --- /dev/null +++ b/urls.py @@ -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[0-9]+)/$', + 'view_ticket', + name='helpdesk_view'), + + url(r'^tickets/(?P[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'), +) diff --git a/views.py b/views.py new file mode 100644 index 00000000..f745d6a8 --- /dev/null +++ b/views.py @@ -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)