* 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:
Ross Poulton 2008-02-08 00:19:58 +00:00
parent 420e9073cf
commit 41a3199513
5 changed files with 136 additions and 37 deletions

25
README
View File

@ -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/.

View File

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

View File

@ -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()

View File

@ -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) &raquo;</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' />

View File

@ -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()