forked from extern/django-helpdesk
* Added ability to attach files
* Via e-mail (and replies) * Via web interface * Updated README with attachment detail * Other various bugfixes as they crop up
This commit is contained in:
parent
420e9073cf
commit
41a3199513
25
README
25
README
@ -10,6 +10,7 @@ Jutda Helpdesk - A Django powered ticket tracker for small enterprise.
|
||||
2. Dependencies (pre-flight checklist)
|
||||
3. Installation
|
||||
4. Initial Configuration
|
||||
5. API Usage
|
||||
|
||||
#########################
|
||||
1. Licensing
|
||||
@ -69,6 +70,20 @@ LICENSE.JQUERY and LICENSE.NICEDIT for their respective license terms.
|
||||
This application assumes all helpdesk media will be accessible at
|
||||
http://MEDIA_PATH/helpdesk/
|
||||
|
||||
7. Inside your MEDIA_ROOT folder, inside the 'helpdesk' folder, is a folder
|
||||
called 'attachments'. Ensure your web server software can write to this
|
||||
folder - something like this should do the trick:
|
||||
|
||||
chown www-data:www-data attachments/; chmod 700 attachments
|
||||
(substitute www-data for the user / group that your web server runs
|
||||
as, eg 'apache' or 'httpd')
|
||||
|
||||
If all else fails ensure all users can write to it:
|
||||
|
||||
chmod 777 attachments/
|
||||
|
||||
This is NOT recommended, especially if you're on a shared server.
|
||||
|
||||
#########################
|
||||
4. Initial Configuration
|
||||
#########################
|
||||
@ -111,3 +126,13 @@ LICENSE.JQUERY and LICENSE.NICEDIT for their respective license terms.
|
||||
This will, on a weekly basis, create exclusions for the coming weekend.
|
||||
|
||||
You're now up and running!
|
||||
|
||||
#########################
|
||||
5. API Usage
|
||||
#########################
|
||||
|
||||
Jutda Helpdesk includes an API accessible via HTTP POST requests, allowing
|
||||
you to create and alter tickets from 3rd party software and systems.
|
||||
|
||||
For usage instructions and command syntax, see the file
|
||||
templates/helpdesk/api_help.html, or visit http://helpdesk/api/help/.
|
||||
|
40
models.py
40
models.py
@ -12,6 +12,7 @@ from datetime import datetime
|
||||
from django.contrib.auth.models import User
|
||||
from django.db import models
|
||||
from django.conf import settings
|
||||
from django.dispatch import dispatcher
|
||||
|
||||
class Queue(models.Model):
|
||||
"""
|
||||
@ -257,18 +258,50 @@ class TicketChange(models.Model):
|
||||
str += 'changed from "%s" to "%s"' % (old_value, new_value)
|
||||
return str
|
||||
|
||||
"""class Attachment(models.Model):
|
||||
|
||||
class DynamicFileField(models.FileField):
|
||||
"""
|
||||
Allows model instance to specify upload_to dynamically.
|
||||
|
||||
Model class should have a method like:
|
||||
|
||||
def get_upload_to(self, attname):
|
||||
return 'path/to/%d' % self.parent.id
|
||||
|
||||
Based on: http://scottbarnham.com/blog/2007/07/31/uploading-images-to-a-dynamic-path-with-django/
|
||||
"""
|
||||
|
||||
def contribute_to_class(self, cls, name):
|
||||
"""Hook up events so we can access the instance."""
|
||||
super(DynamicFileField, self).contribute_to_class(cls, name)
|
||||
dispatcher.connect(self._post_init, models.signals.post_init, sender=cls)
|
||||
|
||||
def _post_init(self, instance=None):
|
||||
"""Get dynamic upload_to value from the model instance."""
|
||||
if hasattr(instance, 'get_upload_to'):
|
||||
self.upload_to = instance.get_upload_to(self.attname)
|
||||
|
||||
def db_type(self):
|
||||
"""Required by Django for ORM."""
|
||||
return 'varchar(100)'
|
||||
|
||||
class Attachment(models.Model):
|
||||
followup = models.ForeignKey(FollowUp, edit_inline=models.TABULAR)
|
||||
file = models.FileField()
|
||||
file = DynamicFileField(upload_to='helpdesk/attachments', core=True)
|
||||
filename = models.CharField(maxlength=100)
|
||||
mime_type = models.CharField(maxlength=30)
|
||||
size = models.IntegerField(help_text='Size of this file in bytes')
|
||||
|
||||
def get_upload_to(self, field_attname):
|
||||
""" Get upload_to path specific to this item """
|
||||
return 'helpdesk/attachments/%s/%s' % (self.followup.ticket.ticket_for_url, self.followup.id)
|
||||
|
||||
def __unicode__(self):
|
||||
return u'%s' % self.filename
|
||||
|
||||
class Meta:
|
||||
ordering = ['filename',] """
|
||||
ordering = ['filename',]
|
||||
|
||||
|
||||
class PreSetReply(models.Model):
|
||||
""" We can allow the admin to define a number of pre-set replies, used to
|
||||
@ -305,3 +338,4 @@ class EscalationExclusion(models.Model):
|
||||
|
||||
def __unicode__(self):
|
||||
return u'%s' % self.name
|
||||
|
||||
|
@ -14,7 +14,7 @@ import imaplib
|
||||
from datetime import datetime, timedelta
|
||||
import email, mimetypes, re
|
||||
from email.Utils import parseaddr
|
||||
from helpdesk.models import Queue, Ticket, FollowUp
|
||||
from helpdesk.models import Queue, Ticket, FollowUp, Attachment
|
||||
from helpdesk.lib import send_multipart_mail
|
||||
|
||||
def process_email():
|
||||
@ -71,6 +71,8 @@ def ticket_from_message(message, queue):
|
||||
sender = message.get('from', 'Unknown Sender')
|
||||
|
||||
sender_email = parseaddr(message.get('from', 'Unknown Sender'))[1]
|
||||
if sender_email.startswith('postmaster'):
|
||||
sender_email = ''
|
||||
|
||||
regex = re.compile("^\[[A-Za-z0-9]+-\d+\]")
|
||||
if regex.match(subject):
|
||||
@ -101,6 +103,7 @@ def ticket_from_message(message, queue):
|
||||
if ticket:
|
||||
try:
|
||||
t = Ticket.objects.get(id=ticket)
|
||||
new = False
|
||||
except:
|
||||
ticket = None
|
||||
|
||||
@ -124,50 +127,51 @@ def ticket_from_message(message, queue):
|
||||
priority=priority,
|
||||
)
|
||||
t.save()
|
||||
new = True
|
||||
update = ''
|
||||
|
||||
context = {
|
||||
'ticket': t,
|
||||
'queue': queue,
|
||||
}
|
||||
context = {
|
||||
'ticket': t,
|
||||
'queue': queue,
|
||||
}
|
||||
|
||||
update = ""
|
||||
if new:
|
||||
|
||||
if sender_email:
|
||||
send_multipart_mail('helpdesk/emails/submitter_newticket', context, '%s %s' % (t.ticket, t.title), sender_email, queue.from_address)
|
||||
send_multipart_mail('helpdesk/emails/submitter_newticket', context, '%s %s' % (t.ticket, t.title), sender_email, queue.from_address, fail_silently=True)
|
||||
|
||||
if queue.new_ticket_cc:
|
||||
send_multipart_mail('helpdesk/emails/cc_newticket', context, '%s %s (Opened)' % (t.ticket, t.title), queue.updated_ticket_cc, queue.from_address)
|
||||
send_multipart_mail('helpdesk/emails/cc_newticket', context, '%s %s (Opened)' % (t.ticket, t.title), queue.updated_ticket_cc, queue.from_address, fail_silently=True)
|
||||
|
||||
if queue.updated_ticket_cc and queue.updated_ticket_cc != queue.new_ticket_cc:
|
||||
send_multipart_mail('helpdesk/emails/cc_newticket', context, '%s %s (Opened)' % (t.ticket, t.title), queue.updated_ticket_cc, queue.from_address)
|
||||
send_multipart_mail('helpdesk/emails/cc_newticket', context, '%s %s (Opened)' % (t.ticket, t.title), queue.updated_ticket_cc, queue.from_address, fail_silently=True)
|
||||
|
||||
else:
|
||||
f = FollowUp(
|
||||
ticket = t,
|
||||
title = 'E-Mail Received from %s' % sender_email,
|
||||
date = datetime.now(),
|
||||
public = True,
|
||||
comment = body,
|
||||
)
|
||||
f.save()
|
||||
update = " (Updated)"
|
||||
|
||||
if t.assigned_to:
|
||||
send_multipart_mail('helpdesk/emails/owner_updated', context, '%s %s (Updated)' % (t.ticket, t.title), t.assigned_to.email, queue.from_address)
|
||||
send_multipart_mail('helpdesk/emails/owner_updated', context, '%s %s (Updated)' % (t.ticket, t.title), t.assigned_to.email, queue.from_address, file_silently=True)
|
||||
|
||||
if queue.updated_ticket_cc:
|
||||
send_multipart_mail('helpdesk/emails/cc_updatedt', context, '%s %s (Updated)' % (t.ticket, t.title), queue.updated_ticket_cc, queue.from_address)
|
||||
send_multipart_mail('helpdesk/emails/cc_updated', context, '%s %s (Updated)' % (t.ticket, t.title), queue.updated_ticket_cc, queue.from_address, fail_silently=True)
|
||||
|
||||
f = FollowUp(
|
||||
ticket = t,
|
||||
title = 'E-Mail Received from %s' % sender_email,
|
||||
date = datetime.now(),
|
||||
public = True,
|
||||
comment = body,
|
||||
)
|
||||
f.save()
|
||||
|
||||
print " [%s-%s] %s%s" % (t.queue.slug, t.id, t.title, update)
|
||||
|
||||
#for file in files:
|
||||
#data = file['content']
|
||||
#filename = file['filename'].replace(' ', '_')
|
||||
#type = file['type']
|
||||
#a = Attachment(followup=f, filename=filename, mimetype=type, size=len(data))
|
||||
#a.save()
|
||||
#print " - %s" % file['filename']
|
||||
for file in files:
|
||||
filename = file['filename'].replace(' ', '_')
|
||||
a = Attachment(followup=f, filename=filename, mime_type=file['type'], size=len(file['content']))
|
||||
a.save_file_file(file['filename'], file['content'])
|
||||
a.save()
|
||||
print " - %s" % file['filename']
|
||||
|
||||
if __name__ == '__main__':
|
||||
process_email()
|
||||
|
@ -10,6 +10,19 @@
|
||||
$("#ShowFurtherOptPara").hide();
|
||||
return false;
|
||||
});
|
||||
|
||||
$("#ShowFileUpload").click(function() {
|
||||
$("#FileUpload").fadeIn();
|
||||
$("#ShowFileUploadPara").hide();
|
||||
return false;
|
||||
});
|
||||
|
||||
$(".AddAnotherFile").click(function() {
|
||||
$(this).hide();
|
||||
$("#FileUpload>dl").append("<dt><label>Attach another File</label></dt><dd><input type='file' name='attachment' id='file' /> (<a href='#' class='AddAnotherFile'>Add Another File</a>)</dd>");
|
||||
return false;
|
||||
});
|
||||
|
||||
$('#id_preset').change(function() {
|
||||
preset = $('#id_preset').val();
|
||||
if (preset != '') {
|
||||
@ -71,18 +84,22 @@
|
||||
<div class='followup'>
|
||||
<div class='title'>{{ followup.title }} <span class='byline'>{% if followup.user %}by {{ followup.user }}{% endif %} <span title='{{ followup.date|date:"r" }}'>{{ followup.date|timesince }} ago</span>{% if not followup.public %} <span class='private'>(Private)</span>{% endif %}</span></div>
|
||||
{% if followup.comment %}{{ followup.comment|num_to_link }}{% endif %}
|
||||
{% if followup.ticketchange_set.all %}<div class='changes'><ul>
|
||||
{% for change in followup.ticketchange_set.all %}
|
||||
{% if forloop.first %}<div class='changes'><ul>{% endif %}
|
||||
<li>Changed {{ change.field }} from {{ change.old_value }} to {{ change.new_value }}.</li>
|
||||
{% if forloop.last %}</div></ul>{% endif %}
|
||||
{% endfor %}
|
||||
{% for attachment in followup.attachment_set.all %}{% if forloop.first %}<div class='attachments'><ul>{% endif %}
|
||||
<li><a href='{{ attachment.get_file_url }}'>{{ attachment.filename }}</a> ({{ attachment.mime_type }}, {{ attachment.size|filesizeformat }})</li>
|
||||
{% if forloop.last %}</ul></div>{% endif %}
|
||||
{% endfor %}
|
||||
</div></ul>{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
<h3>Respond to this ticket</h3>
|
||||
|
||||
<form method='post' action='update/'>
|
||||
<form method='post' action='update/' enctype='multipart/form-data'>
|
||||
|
||||
<fieldset>
|
||||
<dl>
|
||||
@ -139,8 +156,20 @@
|
||||
|
||||
</div>
|
||||
|
||||
</fieldset>
|
||||
<p id='ShowFileUploadPara'><a href='#' id='ShowFileUpload'>Attach File(s) »</a></p>
|
||||
|
||||
<div id='FileUpload' style='display: none;'>
|
||||
|
||||
<dl>
|
||||
|
||||
<dt><label for='id_file'>Attach a File</label></dt>
|
||||
<dd><input type='file' name='attachment' id='file' /> (<a href='#' class='AddAnotherFile'>Add Another File</a>)</dd>
|
||||
|
||||
</dl>
|
||||
|
||||
</div>
|
||||
|
||||
</fieldset>
|
||||
|
||||
<input type='submit' value='Update This Ticket' />
|
||||
|
||||
|
15
views.py
15
views.py
@ -18,7 +18,7 @@ from django.template import loader, Context, RequestContext
|
||||
|
||||
from helpdesk.forms import TicketForm, PublicTicketForm
|
||||
from helpdesk.lib import send_multipart_mail
|
||||
from helpdesk.models import Ticket, Queue, FollowUp, TicketChange, PreSetReply
|
||||
from helpdesk.models import Ticket, Queue, FollowUp, TicketChange, PreSetReply, Attachment
|
||||
|
||||
def dashboard(request):
|
||||
"""
|
||||
@ -172,7 +172,7 @@ def update_ticket(request, ticket_id):
|
||||
else:
|
||||
template = 'helpdesk/emails/submitter_updated'
|
||||
subject = '%s %s (Updated)' % (ticket.ticket, ticket.title)
|
||||
send_multipart_mail(template, context, subject, ticket.submitter_email, ticket.queue.from_address)
|
||||
send_multipart_mail(template, context, subject, ticket.submitter_email, ticket.queue.from_address, fail_silently=True)
|
||||
|
||||
if ticket.assigned_to and request.user != ticket.assigned_to and ticket.assigned_to.email:
|
||||
# We only send e-mails to staff members if the ticket is updated by
|
||||
@ -190,7 +190,7 @@ def update_ticket(request, ticket_id):
|
||||
template_staff = 'helpdesk/emails/owner_updated'
|
||||
subject = '%s %s (Updated)' % (ticket.ticket, ticket.title)
|
||||
|
||||
send_multipart_mail(template_staff, context, subject, ticket.assigned_to.email, ticket.queue.from_address)
|
||||
send_multipart_mail(template_staff, context, subject, ticket.assigned_to.email, ticket.queue.from_address, fail_silently=True)
|
||||
|
||||
|
||||
if ticket.queue.updated_ticket_cc:
|
||||
@ -207,7 +207,14 @@ def update_ticket(request, ticket_id):
|
||||
template_cc = 'helpdesk/emails/cc_updated'
|
||||
subject = '%s %s (Updated)' % (ticket.ticket, ticket.title)
|
||||
|
||||
send_multipart_mail(template_cc, context, subject, ticket.queue.updated_ticket_cc, ticket.queue.from_address)
|
||||
send_multipart_mail(template_cc, context, subject, ticket.queue.updated_ticket_cc, ticket.queue.from_address, fail_silently=True)
|
||||
|
||||
if request.FILES:
|
||||
for file in request.FILES.getlist('attachment'):
|
||||
filename = file['filename'].replace(' ', '_')
|
||||
a = Attachment(followup=f, filename=filename, mime_type=file['content-type'], size=len(file['content']))
|
||||
a.save_file_file(file['filename'], file['content'])
|
||||
a.save()
|
||||
|
||||
ticket.save()
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user