diff --git a/README b/README index 149229d0..cd4a2b0d 100644 --- a/README +++ b/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/. diff --git a/models.py b/models.py index bebfd715..1eec2697 100644 --- a/models.py +++ b/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 + diff --git a/scripts/get_email.py b/scripts/get_email.py index 979bbc85..bbb57008 100644 --- a/scripts/get_email.py +++ b/scripts/get_email.py @@ -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() diff --git a/templates/helpdesk/ticket.html b/templates/helpdesk/ticket.html index 60911727..08994cc9 100644 --- a/templates/helpdesk/ticket.html +++ b/templates/helpdesk/ticket.html @@ -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("