mirror of
https://github.com/django-helpdesk/django-helpdesk.git
synced 2025-05-30 06:29:15 +02:00
Big bugfix release - addresses a number of issues introduced in recent Django
updates, and other bugs in the codebase. Many thanks to David Clymer and Chris Etcp for reporting these bugs and then providing fixes. Tickets closed: #3: BUG E-Mail Script Incompatible with Python 2.5 #4: BUG Failure on empty attachments #5: ENHANCEMENT Run scripts as command extensions [Backwards Compatible] #7: BUG Cannot view tickets when not logged in #8: BUG Overly broad error handling Note that #5 is backwards-incompatible, as you need to change any CRON or scheduler entries for the 'get_email.py', 'escalate_tickets.py' or 'create_escalation_exclusions.py' scripts. See the README file for the new commands.
This commit is contained in:
parent
4583d1c7c8
commit
cea6394b70
22
README
22
README
@ -11,6 +11,7 @@ Jutda Helpdesk - A Django powered ticket tracker for small enterprise.
|
|||||||
3. Installation
|
3. Installation
|
||||||
4. Initial Configuration
|
4. Initial Configuration
|
||||||
5. API Usage
|
5. API Usage
|
||||||
|
6. Thank You
|
||||||
|
|
||||||
#########################
|
#########################
|
||||||
1. Licensing
|
1. Licensing
|
||||||
@ -104,7 +105,7 @@ LICENSE.JQUERY and LICENSE.NICEDIT for their respective license terms.
|
|||||||
Don't forget to set the relevant Django environment variables in your
|
Don't forget to set the relevant Django environment variables in your
|
||||||
crontab:
|
crontab:
|
||||||
|
|
||||||
*/5 * * * * DJANGO_SETTINGS_MODULE='myproject.settings' python /path/to/helpdesk/scripts/get_email.py
|
*/5 * * * * /path/to/helpdesksite/manage.py get_email
|
||||||
|
|
||||||
This will run the e-mail import every 5 minutes
|
This will run the e-mail import every 5 minutes
|
||||||
|
|
||||||
@ -114,7 +115,7 @@ LICENSE.JQUERY and LICENSE.NICEDIT for their respective license terms.
|
|||||||
4. If you wish to automatically escalate tickets based on their age, set up
|
4. If you wish to automatically escalate tickets based on their age, set up
|
||||||
a cronjob to run scripts/escalate_tickets.py on a regular basis:
|
a cronjob to run scripts/escalate_tickets.py on a regular basis:
|
||||||
|
|
||||||
0 * * * * DJANGO_SETTINGS_MODULE='myproject.settings' python /path/to/helpdesk/scripts/escalate_tickets.py
|
0 * * * * /path/to/helpdesksite/manage.py escalate_tickets.py
|
||||||
|
|
||||||
This will run the escalation process hourly, using the 'Escalation Hours'
|
This will run the escalation process hourly, using the 'Escalation Hours'
|
||||||
setting for each queue to determine which tickets to escalate.
|
setting for each queue to determine which tickets to escalate.
|
||||||
@ -123,7 +124,7 @@ LICENSE.JQUERY and LICENSE.NICEDIT for their respective license terms.
|
|||||||
the dates manually via the Admin, or setup a cronjob to run
|
the dates manually via the Admin, or setup a cronjob to run
|
||||||
scripts/create_escalation_exclusions.py on a regular basis:
|
scripts/create_escalation_exclusions.py on a regular basis:
|
||||||
|
|
||||||
0 0 * * 0 DJANGO_SETTINGS_MODULE='myproject.settings' python /path/to/helpdesk/scripts/create_escalation_exclusions.py --days saturday,sunday --verbose
|
0 0 * * 0 /path/to/helpdesksite/manage.py create_escalation_exclusions.py --days saturday,sunday --verbose
|
||||||
|
|
||||||
This will, on a weekly basis, create exclusions for the coming weekend.
|
This will, on a weekly basis, create exclusions for the coming weekend.
|
||||||
|
|
||||||
@ -138,3 +139,18 @@ you to create and alter tickets from 3rd party software and systems.
|
|||||||
|
|
||||||
For usage instructions and command syntax, see the file
|
For usage instructions and command syntax, see the file
|
||||||
templates/helpdesk/api_help.html, or visit http://helpdesk/api/help/.
|
templates/helpdesk/api_help.html, or visit http://helpdesk/api/help/.
|
||||||
|
|
||||||
|
#########################
|
||||||
|
6. Thank You
|
||||||
|
#########################
|
||||||
|
|
||||||
|
While this started as a project to suit my own needs, since publishing the
|
||||||
|
code a number of people have made some fantastic improvements and provided
|
||||||
|
bug fixes and updates as the Django codebase has moved on and caused small
|
||||||
|
portions of this application to break.
|
||||||
|
|
||||||
|
To these people, my sincere thanks:
|
||||||
|
|
||||||
|
David Clymer <http://djangopeople.net/vezult/>
|
||||||
|
Chris Etcp
|
||||||
|
Nikolay Panov
|
||||||
|
2
forms.py
2
forms.py
@ -57,7 +57,7 @@ class TicketForm(forms.Form):
|
|||||||
try:
|
try:
|
||||||
u = User.objects.get(id=self.cleaned_data['assigned_to'])
|
u = User.objects.get(id=self.cleaned_data['assigned_to'])
|
||||||
t.assigned_to = u
|
t.assigned_to = u
|
||||||
except:
|
except User.DoesNotExist:
|
||||||
t.assigned_to = None
|
t.assigned_to = None
|
||||||
t.save()
|
t.save()
|
||||||
|
|
||||||
|
0
management/commands/__init__.py
Normal file
0
management/commands/__init__.py
Normal file
@ -13,6 +13,58 @@ from django.db.models import Q
|
|||||||
from helpdesk.models import EscalationExclusion, Queue
|
from helpdesk.models import EscalationExclusion, Queue
|
||||||
import sys, getopt
|
import sys, getopt
|
||||||
|
|
||||||
|
from django.core.management.base import BaseCommand, CommandError
|
||||||
|
from optparse import make_option
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
def __init__(self):
|
||||||
|
BaseCommand.__init__(self)
|
||||||
|
|
||||||
|
self.option_list += (
|
||||||
|
make_option(
|
||||||
|
'--days', '-d',
|
||||||
|
help='Days of week (monday, tuesday, etc)'),
|
||||||
|
make_option(
|
||||||
|
'--occurrences', '-o',
|
||||||
|
type='int',
|
||||||
|
default=1,
|
||||||
|
help='Occurrences: How many weeks ahead to exclude this day'),
|
||||||
|
make_option(
|
||||||
|
'--queues', '-q',
|
||||||
|
help='Queues to include (default: all). Use queue slugs'),
|
||||||
|
make_option(
|
||||||
|
'--verbose', '-v',
|
||||||
|
action='store_true',
|
||||||
|
default=False,
|
||||||
|
help='Display a list of dates excluded'),
|
||||||
|
)
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
days = options['days']
|
||||||
|
occurrences = options['occurrences']
|
||||||
|
verbose = False
|
||||||
|
queue_slugs = options['queues']
|
||||||
|
queues = []
|
||||||
|
|
||||||
|
if options['verbose']:
|
||||||
|
verbose = True
|
||||||
|
|
||||||
|
# this should already be handled by optparse
|
||||||
|
if not occurrences: occurrences = 1
|
||||||
|
if not (days and occurrences):
|
||||||
|
raise CommandError('One or more occurrences must be specified.')
|
||||||
|
|
||||||
|
if queue_slugs is not None:
|
||||||
|
queue_set = queue_slugs.split(',')
|
||||||
|
for queue in queue_set:
|
||||||
|
try:
|
||||||
|
q = Queue.objects.get(slug__exact=queue)
|
||||||
|
except Queue.DoesNotExist:
|
||||||
|
raise CommandError("Queue %s does not exist." % queue)
|
||||||
|
queues.append(q)
|
||||||
|
|
||||||
|
create_exclusions(days=days, occurrences=occurrences, verbose=verbose, queues=queues)
|
||||||
|
|
||||||
day_names = {
|
day_names = {
|
||||||
'monday': 0,
|
'monday': 0,
|
||||||
'tuesday': 1,
|
'tuesday': 1,
|
||||||
@ -88,7 +140,7 @@ if __name__ == '__main__':
|
|||||||
for queue in queue_set:
|
for queue in queue_set:
|
||||||
try:
|
try:
|
||||||
q = Queue.objects.get(slug__exact=queue)
|
q = Queue.objects.get(slug__exact=queue)
|
||||||
except:
|
except Queue.DoesNotExist:
|
||||||
print "Queue %s does not exist." % queue
|
print "Queue %s does not exist." % queue
|
||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
queues.append(q)
|
queues.append(q)
|
@ -15,6 +15,45 @@ from django.utils.translation import ugettext as _
|
|||||||
|
|
||||||
from helpdesk.models import Queue, Ticket, FollowUp, EscalationExclusion, TicketChange
|
from helpdesk.models import Queue, Ticket, FollowUp, EscalationExclusion, TicketChange
|
||||||
from helpdesk.lib import send_templated_mail
|
from helpdesk.lib import send_templated_mail
|
||||||
|
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
from optparse import make_option
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
def __init__(self):
|
||||||
|
BaseCommand.__init__(self)
|
||||||
|
|
||||||
|
self.option_list += (
|
||||||
|
make_option(
|
||||||
|
'--queues', '-q',
|
||||||
|
help='Queues to include (default: all). Use queue slugs'),
|
||||||
|
make_option(
|
||||||
|
'--verbose', '-v',
|
||||||
|
action='store_true',
|
||||||
|
default=False,
|
||||||
|
help='Display a list of dates excluded'),
|
||||||
|
)
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
verbose = False
|
||||||
|
queue_slugs = None
|
||||||
|
queues = []
|
||||||
|
|
||||||
|
if options['verbose']:
|
||||||
|
verbose = True
|
||||||
|
if options['queues']:
|
||||||
|
queue_slugs = options['queues']
|
||||||
|
|
||||||
|
if queue_slugs is not None:
|
||||||
|
queue_set = queue_slugs.split(',')
|
||||||
|
for queue in queue_set:
|
||||||
|
try:
|
||||||
|
q = Queue.objects.get(slug__exact=queue)
|
||||||
|
except Queue.DoesNotExist:
|
||||||
|
raise CommandError("Queue %s does not exist." % queue)
|
||||||
|
queues.append(queue)
|
||||||
|
|
||||||
|
escalate_tickets(queues=queues, verbose=verbose)
|
||||||
|
|
||||||
def escalate_tickets(queues, verbose):
|
def escalate_tickets(queues, verbose):
|
||||||
""" Only include queues with escalation configured """
|
""" Only include queues with escalation configured """
|
||||||
@ -47,7 +86,7 @@ def escalate_tickets(queues, verbose):
|
|||||||
|
|
||||||
context = {
|
context = {
|
||||||
'ticket': t,
|
'ticket': t,
|
||||||
'queue': queue,
|
'queue': q,
|
||||||
}
|
}
|
||||||
|
|
||||||
if t.submitter_email:
|
if t.submitter_email:
|
||||||
@ -106,7 +145,7 @@ if __name__ == '__main__':
|
|||||||
for queue in queue_set:
|
for queue in queue_set:
|
||||||
try:
|
try:
|
||||||
q = Queue.objects.get(slug__exact=queue)
|
q = Queue.objects.get(slug__exact=queue)
|
||||||
except:
|
except Queue.DoesNotExist:
|
||||||
print "Queue %s does not exist." % queue
|
print "Queue %s does not exist." % queue
|
||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
queues.append(queue)
|
queues.append(queue)
|
@ -19,6 +19,12 @@ from helpdesk.lib import send_templated_mail
|
|||||||
|
|
||||||
from django.core.files.base import ContentFile
|
from django.core.files.base import ContentFile
|
||||||
|
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
process_email()
|
||||||
|
|
||||||
def process_email():
|
def process_email():
|
||||||
for q in Queue.objects.filter(email_box_type__isnull=False, allow_email_submission=True):
|
for q in Queue.objects.filter(email_box_type__isnull=False, allow_email_submission=True):
|
||||||
if not q.email_box_last_check: q.email_box_last_check = datetime.now()-timedelta(minutes=30)
|
if not q.email_box_last_check: q.email_box_last_check = datetime.now()-timedelta(minutes=30)
|
||||||
@ -89,7 +95,7 @@ def ticket_from_message(message, queue):
|
|||||||
counter = 0
|
counter = 0
|
||||||
files = []
|
files = []
|
||||||
for part in message.walk():
|
for part in message.walk():
|
||||||
if part.get_main_type() == 'multipart':
|
if part.get_content_maintype() == 'multipart':
|
||||||
continue
|
continue
|
||||||
|
|
||||||
name = part.get_param("name")
|
name = part.get_param("name")
|
||||||
@ -97,7 +103,7 @@ def ticket_from_message(message, queue):
|
|||||||
if part.get_content_maintype() == 'text' and name == None:
|
if part.get_content_maintype() == 'text' and name == None:
|
||||||
body = part.get_payload()
|
body = part.get_payload()
|
||||||
else:
|
else:
|
||||||
if name == None:
|
if not name:
|
||||||
ext = mimetypes.guess_extension(part.get_content_type())
|
ext = mimetypes.guess_extension(part.get_content_type())
|
||||||
name = "part-%i%s" % (counter, ext)
|
name = "part-%i%s" % (counter, ext)
|
||||||
files.append({'filename': name, 'content': part.get_payload(decode=True), 'type': part.get_content_type()})
|
files.append({'filename': name, 'content': part.get_payload(decode=True), 'type': part.get_content_type()})
|
||||||
@ -110,7 +116,7 @@ def ticket_from_message(message, queue):
|
|||||||
try:
|
try:
|
||||||
t = Ticket.objects.get(id=ticket)
|
t = Ticket.objects.get(id=ticket)
|
||||||
new = False
|
new = False
|
||||||
except:
|
except Ticket.DoesNotExist:
|
||||||
ticket = None
|
ticket = None
|
||||||
|
|
||||||
priority = 3
|
priority = 3
|
@ -13,7 +13,7 @@
|
|||||||
|
|
||||||
<table width='100%'>
|
<table width='100%'>
|
||||||
<tr class='row_tablehead'><td colspan='2'>{{ ticket.id }}. {{ ticket.title }} [{{ ticket.get_status }}]</td></tr>
|
<tr class='row_tablehead'><td colspan='2'>{{ ticket.id }}. {{ ticket.title }} [{{ ticket.get_status }}]</td></tr>
|
||||||
<tr class='row_columnheads'><th colspan='2'>{% blocktrans %}Queue: {{ ticket.queue }}{% endblocktrans %}</th></tr>
|
<tr class='row_columnheads'><th colspan='2'>{% blocktrans with ticket.queue as queue_name %}Queue: {{ queue_name }}{% endblocktrans %}</th></tr>
|
||||||
|
|
||||||
<tr class='row_odd'>
|
<tr class='row_odd'>
|
||||||
<th>{% trans "Submitted On" %}</th>
|
<th>{% trans "Submitted On" %}</th>
|
||||||
|
@ -41,7 +41,7 @@ def num_to_link(text):
|
|||||||
url = reverse('helpdesk_view', args=[number])
|
url = reverse('helpdesk_view', args=[number])
|
||||||
try:
|
try:
|
||||||
ticket = Ticket.objects.get(id=number)
|
ticket = Ticket.objects.get(id=number)
|
||||||
except:
|
except Ticket.DoesNotExist:
|
||||||
ticket = None
|
ticket = None
|
||||||
|
|
||||||
if ticket:
|
if ticket:
|
||||||
|
12
views/api.py
12
views/api.py
@ -107,7 +107,7 @@ class API:
|
|||||||
try:
|
try:
|
||||||
u = User.objects.get(username=username)
|
u = User.objects.get(username=username)
|
||||||
return api_return(STATUS_OK, "%s" % u.id)
|
return api_return(STATUS_OK, "%s" % u.id)
|
||||||
except:
|
except User.DoesNotExist:
|
||||||
return api_return(STATUS_ERROR, "Invalid username provided")
|
return api_return(STATUS_ERROR, "Invalid username provided")
|
||||||
|
|
||||||
|
|
||||||
@ -117,7 +117,7 @@ class API:
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
ticket = Ticket.objects.get(id=self.request.POST.get('ticket', False))
|
ticket = Ticket.objects.get(id=self.request.POST.get('ticket', False))
|
||||||
except:
|
except Ticket.DoesNotExist:
|
||||||
return api_return(STATUS_ERROR, "Invalid ticket ID")
|
return api_return(STATUS_ERROR, "Invalid ticket ID")
|
||||||
|
|
||||||
ticket.delete()
|
ticket.delete()
|
||||||
@ -127,7 +127,7 @@ class API:
|
|||||||
def api_public_hold_ticket(self):
|
def api_public_hold_ticket(self):
|
||||||
try:
|
try:
|
||||||
ticket = Ticket.objects.get(id=self.request.POST.get('ticket', False))
|
ticket = Ticket.objects.get(id=self.request.POST.get('ticket', False))
|
||||||
except:
|
except Ticket.DoesNotExist:
|
||||||
return api_return(STATUS_ERROR, "Invalid ticket ID")
|
return api_return(STATUS_ERROR, "Invalid ticket ID")
|
||||||
|
|
||||||
ticket.on_hold = True
|
ticket.on_hold = True
|
||||||
@ -139,7 +139,7 @@ class API:
|
|||||||
def api_public_unhold_ticket(self):
|
def api_public_unhold_ticket(self):
|
||||||
try:
|
try:
|
||||||
ticket = Ticket.objects.get(id=self.request.POST.get('ticket', False))
|
ticket = Ticket.objects.get(id=self.request.POST.get('ticket', False))
|
||||||
except:
|
except Ticket.DoesNotExist:
|
||||||
return api_return(STATUS_ERROR, "Invalid ticket ID")
|
return api_return(STATUS_ERROR, "Invalid ticket ID")
|
||||||
|
|
||||||
ticket.on_hold = False
|
ticket.on_hold = False
|
||||||
@ -151,7 +151,7 @@ class API:
|
|||||||
def api_public_add_followup(self):
|
def api_public_add_followup(self):
|
||||||
try:
|
try:
|
||||||
ticket = Ticket.objects.get(id=self.request.POST.get('ticket', False))
|
ticket = Ticket.objects.get(id=self.request.POST.get('ticket', False))
|
||||||
except:
|
except Ticket.DoesNotExist:
|
||||||
return api_return(STATUS_ERROR, "Invalid ticket ID")
|
return api_return(STATUS_ERROR, "Invalid ticket ID")
|
||||||
|
|
||||||
message = self.request.POST.get('message', None)
|
message = self.request.POST.get('message', None)
|
||||||
@ -191,7 +191,7 @@ class API:
|
|||||||
def api_public_resolve(self):
|
def api_public_resolve(self):
|
||||||
try:
|
try:
|
||||||
ticket = Ticket.objects.get(id=self.request.POST.get('ticket', False))
|
ticket = Ticket.objects.get(id=self.request.POST.get('ticket', False))
|
||||||
except:
|
except Ticket.DoesNotExist:
|
||||||
return api_return(STATUS_ERROR, "Invalid ticket ID")
|
return api_return(STATUS_ERROR, "Invalid ticket ID")
|
||||||
|
|
||||||
resolution = self.request.POST.get('resolution', None)
|
resolution = self.request.POST.get('resolution', None)
|
||||||
|
@ -48,7 +48,7 @@ def view_ticket(request):
|
|||||||
t = Ticket.objects.get(id=ticket_id, queue__slug__iexact=queue, submitter_email__iexact=email)
|
t = Ticket.objects.get(id=ticket_id, queue__slug__iexact=queue, submitter_email__iexact=email)
|
||||||
return render_to_response('helpdesk/public_view_ticket.html',
|
return render_to_response('helpdesk/public_view_ticket.html',
|
||||||
RequestContext(request, {'ticket': t,}))
|
RequestContext(request, {'ticket': t,}))
|
||||||
except:
|
except Ticket.DoesNotExist:
|
||||||
t = False;
|
t = False;
|
||||||
error_message = _('Invalid ticket ID or e-mail address. Please try again.')
|
error_message = _('Invalid ticket ID or e-mail address. Please try again.')
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user