diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..08586901 --- /dev/null +++ b/LICENSE @@ -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$ + +LICENSING INFORMATION + +This software is not Free Software. You may freely download this software from it's manufacturer (Jutda, www.jutda.com.au) and use it for your own purposes. You may not distribute this software or give it to others. + +You may download and install this software for other users, for example for a client or customer of yours or your companies. + + diff --git a/README b/README new file mode 100644 index 00000000..9a008943 --- /dev/null +++ b/README @@ -0,0 +1,105 @@ + .. + .,::;:::::: + ..,::::::::,,,,::: 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$ + +######################### +0. Table of Contents +######################### + +1. Licensing +2. Dependencies (pre-flight checklist) +3. Installation +4. Initial Configuration + +######################### +1. Licensing +######################### + +See the file 'LICENSE' for licensing terms and conditions. + +######################### +2. Dependencies (pre-flight checklist) +######################### +1. Python 2.3+ + +2. Django (post-0.96, eg SVN checkout) + +3. Markdown + +4. An existing WORKING Django project with database etc + + +######################### +3. Installation +######################### + +1. Place 'helpdesk' in your Python path + +2. In your projects' settings.py file, add these lines to the INSTALLED_APPS setting: + 'helpdesk', + 'django.contrib.admin', + 'django.contrib.markup' + +3. In your projects' urls.py file, add this line: + (r'helpdesk/', include('helpdesk.urls')), + + You can substitute 'helpdesk/' for something else, eg 'support/' or even ''. + +4. Ensure the admin line is un-hashed in urls.py: + # Uncomment this for admin: + (r'^admin/', include('django.contrib.admin.urls')), + + If you use helpdesk at the top of your domain (at /), ensure the admin + line comes BEFORE the helpdesk line. + +5. In your project directory (NOT the helpdesk directory) run + ./manage.py syncdb + to create database tables + +######################### +4. Initial Configuration +######################### + +1. Visit http://yoursite/admin/ and add a Helpdesk Queue. If you wish, + enter your POP3 or IMAP server details. + + IMPORTANT NOTE: Any tickets created via POP3 or IMAP mailboxes will DELETE + the original e-mail from the mail server. + +2. Visit http://yoursite/helpdesk/ (or other path as defined in your urls.py) + +3. If you wish to automatically create tickets from the contents of an e-mail + inbox, set up a cronjob to run scripts/get_email.py on a regular basis. + + Don't forget to set the relevant Django environment variables in your + crnotab: + + 5 * * * * DJANGO_SETTINGS_MODULE='myproject.settings' python /path/to/helpdesk/scripts/get_email.py + + IMPORTANT NOTE: Any tickets created via POP3 or IMAP mailboxes will DELETE + the original e-mail from the mail server. + +You're now up and running! diff --git a/lib.py b/lib.py new file mode 100644 index 00000000..1beba30b --- /dev/null +++ b/lib.py @@ -0,0 +1,78 @@ +""" .. + .,::;:::::: + ..,::::::::,,,,::: 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$ +""" + +def send_multipart_mail(template_name, email_context, subject, recipients, sender=None, bcc=None, fail_silently=False, files=None): + """ + This function will send a multi-part e-mail with both HTML and + Text parts. + + template_name must NOT contain an extension. Both HTML (.html) and TEXT + (.txt) versions must exist, eg 'emails/public_submit' will use both + public_submit.html and public_submit.txt. + + email_context should be a plain python dictionary. It is applied against + both the email messages (templates) & the subject. + + subject can be plain text or a Django template string, eg: + New Job: {{ job.id }} {{ job.title }} + + recipients can be either a string, eg 'a@b.com' or a list, eg: + ['a@b.com', 'c@d.com']. Type conversion is done if needed. + + sender can be an e-mail, 'Name ' or None. If unspecified, the + DEFAULT_FROM_EMAIL will be used. + + """ + from django.core.mail import EmailMultiAlternatives + from django.template import loader, Context + from django.conf import settings + + if not sender: + sender = settings.DEFAULT_FROM_EMAIL + + context = Context(email_context) + + text_part = loader.get_template('%s.txt' % template_name).render(context) + html_part = loader.get_template('%s.html' % template_name).render(context) + subject_part = loader.get_template_from_string(subject).render(context) + + if type(recipients) != list: + recipients = [recipients,] + + msg = EmailMultiAlternatives(subject_part, text_part, sender, recipients, bcc=bcc) + msg.attach_alternative(html_part, "text/html") + + if files: + if type(files) != list: + files = [files,] + + for file in files: + msg.attach_file(file) + + return msg.send(fail_silently) + diff --git a/models.py b/models.py index 41fbeb62..45848b8e 100644 --- a/models.py +++ b/models.py @@ -46,12 +46,29 @@ class Queue(models.Model): slug = models.SlugField() email_address = models.EmailField(blank=True, null=True) + def _from_address(self): + return '%s <%s>' % (self.title, self.email_address) + from_address = property(_from_address) + + email_box_type = models.CharField(maxlength=5, choices=(('pop3', 'POP 3'),('imap', 'IMAP')), blank=True, null=True, help_text='E-Mail Server Type - Both POP3 and IMAP are supported. Select your email server type here.') + email_box_host = models.CharField(maxlength=200, blank=True, null=True, help_text='Your e-mail server address - either the domain name or IP address. May be "localhost".') + email_box_port = models.IntegerField(blank=True, null=True, help_text='Port number to use for accessing e-mail. Default for POP3 is "110", and for IMAP is "143". This may differ on some servers.') + email_box_user = models.CharField(maxlength=200, blank=True, null=True, help_text='Username for accessing this mailbox.') + email_box_pass = models.CharField(maxlength=200, blank=True, null=True, help_text='Password for the above username') + email_box_imap_folder = models.CharField(maxlength=100, blank=True, null=True, help_text='If using IMAP, what folder do you wish to fetch messages from? This allows you to use one IMAP account for multiple queues, by filtering messages on your IMAP server into separate folders. Default: INBOX.') + email_box_interval = models.IntegerField(help_text='How often do you wish to check this mailbox? (in Minutes)', blank=True, null=True, default='5') + email_box_last_check = models.DateTimeField(blank=True, null=True, editable=False) # Updated by the auto-pop3-and-imap-checker + def __unicode__(self): return u"%s" % self.title class Admin: pass - + + def save(self): + if self.email_box_type == 'imap' and not self.email_box_imap_folder: + self.email_box_imap_folder = 'INBOX' + super(Queue, self).save() class Ticket(models.Model): """ @@ -105,6 +122,13 @@ class Ticket(models.Model): return self.assigned_to get_assigned_to = property(_get_assigned_to) + def _get_ticket(self): + """ A user-friendly ticket ID, which is a combination of ticket ID + and queue slug. This is generally used in e-mails. """ + + return "[%s-%s]" % (self.queue.slug, self.id) + ticket = property(_get_ticket) + class Admin: list_display = ('title', 'status', 'assigned_to',) date_hierarchy = 'created' @@ -175,3 +199,7 @@ class TicketChange(models.Model): else: str += 'changed from "%s" to "%s"' % (old_value, new_value) return str + +#class Attachment(models.Model): + #followup = models.ForeignKey(FollowUp, edit_inline=models.TABULAR) + #file = models.FileField() diff --git a/scripts/get_email.py b/scripts/get_email.py new file mode 100644 index 00000000..dca1f5ff --- /dev/null +++ b/scripts/get_email.py @@ -0,0 +1,125 @@ +import poplib +import imaplib +from datetime import datetime, timedelta +import email, mimetypes, re +from email.Utils import parseaddr +from helpdesk.models import Queue,Ticket +from helpdesk.lib import send_multipart_mail + +def process_email(): + for q in Queue.objects.filter(email_box_type__isnull=False): + if not q.email_box_last_check: q.email_box_last_check = datetime.now()-timedelta(minutes=30) + if not q.email_box_interval: q.email_box_interval = 0 + + if (q.email_box_last_check + timedelta(minutes=q.email_box_interval)) > datetime.now(): + continue + print "Processing: %s" % q + if q.email_box_type == 'pop3': + server = poplib.POP3(q.email_box_host) + server.getwelcome() + server.user(q.email_box_user) + server.pass_(q.email_box_pass) + + messagesInfo = server.list()[1] + + for msg in messagesInfo: + msgNum = msg.split(" ")[0] + msgSize = msg.split(" ")[1] + + full_message = "\n".join(server.retr(msgNum)[1]) + ticket_from_message(message=full_message, queue=q) + + server.dele(msgNum) + server.quit() + + elif q.email_box_type == 'imap': + if not q.email_box_port: q.email_box_port = 143 + + server = imaplib.IMAP4(q.email_box_host, q.email_box_port) + server.login(q.email_box_user, q.email_box_pass) + server.select(q.email_box_imap_folder) + status, data = server.search(None, 'ALL') + for num in data[0].split(): + status, data = server.fetch(num, '(RFC822)') + ticket_from_message(message=data[0][1], queue=q) + server.store(num, '+FLAGS', '\\Deleted') + server.expunge() + server.close() + server.logout() + + q.email_box_last_check = datetime.now() + q.save() + +def ticket_from_message(message, queue): + # 'message' must be an RFC822 formatted message. + msg = message + message = email.message_from_string(msg) + subject = message.get('subject', 'Created from e-mail') + subject = subject.replace("Re: ", "").replace("Fw: ", "").strip() + + sender = message.get('from', 'Unknown Sender') + + sender_email = parseaddr(message.get('from', 'Unknown Sender'))[1] + + regex = re.compile("^\[\d+\]") + if regex.match(subject): + # This is a reply or forward. + ticket = re.match(r"^\[(?P\d+)\]", subject).group('id') + else: + ticket = None + counter = 0 + files = [] + for part in message.walk(): + if part.get_main_type() == 'multipart': + continue + + name = part.get_param("name") + + if part.get_content_maintype() == 'text' and name == None: + body = part.get_payload() + else: + if name == None: + ext = mimetypes.guess_extension(part.get_content_type()) + name = "part-%i%s" % (counter, ext) + files.append({'filename': name, 'content': part.get_payload(decode=True), 'type': part.get_content_type()}) + + counter += 1 + + now = datetime.now() + + if ticket: + try: + t = Ticket.objects.get(id=ticket) + except: + ticket = None + + if ticket == None: + t = Ticket( + title=subject, + queue=queue, + submitter_email=sender_email, + created=now, + description=body, + ) + t.save() + + context = { + 'ticket': t, + 'queue': queue, + } + + if sender_email: + send_multipart_mail('helpdesk/emails/submitter_newticket', context, '%s %s' % (t.ticket, t.title), sender_email, queue.from_address) + + print " [%s-%s] %s" % (t.queue.slug, t.id, t.title) + + #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'] + +if __name__ == '__main__': + process_email() diff --git a/templates/helpdesk/base.html b/templates/helpdesk/base.html index ee053c86..b035173d 100644 --- a/templates/helpdesk/base.html +++ b/templates/helpdesk/base.html @@ -13,7 +13,7 @@
  • Dashboard
  • Tickets
  • Submit Ticket
  • -
  • Search
  • + {% if not query %}
  • {% endif %}
    diff --git a/templates/helpdesk/dashboard.html b/templates/helpdesk/dashboard.html index 524540c6..6dfeee68 100644 --- a/templates/helpdesk/dashboard.html +++ b/templates/helpdesk/dashboard.html @@ -40,7 +40,7 @@ $(document).ready(function() { {{ ticket.id }} {{ ticket.title }} {{ ticket.queue }} -{{ ticket.created|timesince }} ago +{{ ticket.created|timesince }} ago Take {% endfor %} diff --git a/templates/helpdesk/emails/base.html b/templates/helpdesk/emails/base.html new file mode 100644 index 00000000..d88d39d9 --- /dev/null +++ b/templates/helpdesk/emails/base.html @@ -0,0 +1,9 @@ +

    {% block header %}Helpdesk{% endblock %}

    + +{% block content %}{% endblock %} + +

    Regards,

    + +

    {{ queue.title }}{% if queue.email_address %}
    {{ queue.email_address }}{% endif %}

    + +

    This e-mail was sent to you as a user of our support service, in accordance with our privacy policy. Please advise us if you believe you have received this e-mail in error.

    diff --git a/templates/helpdesk/emails/sample.html b/templates/helpdesk/emails/sample.html new file mode 100644 index 00000000..25d86adc --- /dev/null +++ b/templates/helpdesk/emails/sample.html @@ -0,0 +1,9 @@ +{% extends "helpdesk/emails/base.html" %} + +{% block header %}E-Mail Heading Here{% endblock %} + +{% block content %} +

    Dear User,

    + +

    This is some content.

    +{% endblock %} diff --git a/templates/helpdesk/emails/submitter_newticket.html b/templates/helpdesk/emails/submitter_newticket.html new file mode 100644 index 00000000..6d6bbd4e --- /dev/null +++ b/templates/helpdesk/emails/submitter_newticket.html @@ -0,0 +1,17 @@ +{% extends "helpdesk/emails/base.html" %} + +{% block header %}Thank You For Your Submission{% endblock %} + +{% block content %} +

    Hello,

    + + +

    This is a courtesy e-mail to let you know that we have received your helpdesk query with a subject of {{ ticket.title }}.

    + +

    You do not have to do anything further at this stage. Your ticket has been assigned a number of {{ ticket.ticket }}.

    + +

    If you wish to send us further details, or if you have any queries about this ticket, please include the ticket id of {{ ticket.ticket }} in the subject. The easiest way to do this is just press 'reply' to this message.

    + +

    We will investigate your query and attempt to resolve it as soon as possible. You will receive further updates and a resolution via this e-mail address.

    + +{% endblock %} diff --git a/templates/helpdesk/emails/submitter_newticket.txt b/templates/helpdesk/emails/submitter_newticket.txt new file mode 100644 index 00000000..e2ff4b0a --- /dev/null +++ b/templates/helpdesk/emails/submitter_newticket.txt @@ -0,0 +1,11 @@ +Hello, + +This is a courtesy e-mail to let you know that we have received your helpdesk query with a subject of {{ ticket.title }}. + +You do not have to do anything further at this stage. Your ticket has been assigned a number of {{ ticket.ticket }}. + +If you wish to send us further details, or if you have any queries about this ticket, please include the ticket id of {{ ticket.ticket }} in the subject. The easiest way to do this is just press 'reply' to this message. + +We will investigate your query and attempt to resolve it as soon as possible. You will receive further updates and a resolution via this e-mail address. + +{% include "helpdesk/emails/text_footer.txt" %} diff --git a/templates/helpdesk/emails/submitter_resolved.html b/templates/helpdesk/emails/submitter_resolved.html new file mode 100644 index 00000000..4cbf1a8f --- /dev/null +++ b/templates/helpdesk/emails/submitter_resolved.html @@ -0,0 +1,16 @@ +{% extends "helpdesk/emails/base.html" %} + +{% block header %}Your Ticket Has Been Resolved{% endblock %} + +{% block content %} +

    Hello,

    + +

    You recently logged a ticket with a subject of {{ ticket.title }} with us. This e-mail is to advise you of a resolution to that ticket.

    + +

    The following resolution was added to ticket {{ ticket.ticket }}:

    + +
    {{ resolution }}
    + +

    Can you please confirm that this resolution addresses your needs so we may close this ticket? If you have any further queries, or if you do not believe this resolution is adequate, please reply to this e-mail and keep the subject intact.

    + +{% endblock %} diff --git a/templates/helpdesk/emails/submitter_resolved.txt b/templates/helpdesk/emails/submitter_resolved.txt new file mode 100644 index 00000000..96d5d9bc --- /dev/null +++ b/templates/helpdesk/emails/submitter_resolved.txt @@ -0,0 +1,11 @@ +Hello, + +You recently logged a ticket with a subject of '{{ ticket.title }}' with us. This e-mail is to advise you of a resolution to that ticket. + +The following resolution was added to ticket {{ ticket.ticket }}: + +{{ resolution }} + +Can you please confirm that this resolution addresses your needs so we may close this ticket? If you have any further queries, or if you do not believe this resolution is adequate, please reply to this e-mail and keep the subject intact. + +{% include "helpdesk/emails/text_footer.txt" %} diff --git a/templates/helpdesk/emails/submitter_updated.html b/templates/helpdesk/emails/submitter_updated.html new file mode 100644 index 00000000..a48a6233 --- /dev/null +++ b/templates/helpdesk/emails/submitter_updated.html @@ -0,0 +1,16 @@ +{% extends "helpdesk/emails/base.html" %} + +{% block header %}Your Ticket Has Been Updated{% endblock %} + +{% block content %} +

    Hello,

    + +

    You recently logged a ticket with a subject of {{ ticket.title }} with us. This e-mail is to advise you of an update to that ticket.

    + +

    The following comment was added to ticket {{ ticket.ticket }}:

    + +
    {{ comment }}
    + +

    To provide us with further information, please reply to this e-mail.

    + +{% endblock %} diff --git a/templates/helpdesk/emails/submitter_updated.txt b/templates/helpdesk/emails/submitter_updated.txt new file mode 100644 index 00000000..40e1bf44 --- /dev/null +++ b/templates/helpdesk/emails/submitter_updated.txt @@ -0,0 +1,11 @@ +Hello, + +You recently logged a ticket with a subject of '{{ ticket.title }}' with us. This e-mail is to advise you of an update to that ticket. + +The following comment was added to ticket {{ ticket.ticket }}: + +{{ comment }} + +To provide us with further information, please reply to this e-mail. + +{% include "helpdesk/emails/text_footer.txt" %} diff --git a/templates/helpdesk/emails/text_footer.txt b/templates/helpdesk/emails/text_footer.txt new file mode 100644 index 00000000..e44639b5 --- /dev/null +++ b/templates/helpdesk/emails/text_footer.txt @@ -0,0 +1,6 @@ +Regards, + +{{ queue.title }}{% if queue.email_address %} +{{ queue.email_address }}{% endif %} + +This e-mail was sent to you as a user of our support service, in accordance with our privacy policy. Please advise us if you believe you have received this e-mail in error. diff --git a/templates/helpdesk/ticket.html b/templates/helpdesk/ticket.html index 74ba500c..f7b7cea3 100644 --- a/templates/helpdesk/ticket.html +++ b/templates/helpdesk/ticket.html @@ -22,17 +22,31 @@ Submitted On - {{ ticket.created }} ({{ ticket.created|timesince }} ago) + {{ ticket.created|date:"r" }} ({{ ticket.created|timesince }} ago) Assigned To - {{ ticket.get_assigned_to }} + {{ ticket.get_assigned_to }}{% ifequal ticket.get_assigned_to 'Unassigned' %} Take{% endifequal %} -{% if ticket.submitter_email %} + Submitter E-Mail {{ ticket.submitter_email }} + + + + Description + + + {{ ticket.description }} + + +{% if ticket.resolution %} + Resolution{% ifequal ticket.get_status_display "Resolved" %} [Accept & Close]{% endifequal %} + + + {{ ticket.resolution }} {% endif %} @@ -42,7 +56,7 @@ {% load ticket_to_link %} {% for followup in ticket.followup_set.all %}
    -
    {{ followup.title }}
    +
    {{ followup.title }}
    {{ followup.comment|markdown|num_to_link }} {% if followup.ticketchange_set.all %}
    {% for change in followup.ticketchange_set.all %} @@ -63,18 +77,18 @@ Changed {{ change.field }} from {{ change.old_value }} to {{ change.new_value }} {% endifequal %} {% ifequal ticket.status 2 %} - - + » + » {% endifequal %} {% ifequal ticket.status 3 %} - - + « + » {% endifequal %} {% ifequal ticket.status 4 %} - + « {% endifequal %} diff --git a/templates/helpdesk/ticket_list.html b/templates/helpdesk/ticket_list.html index 67054416..33c5a356 100644 --- a/templates/helpdesk/ticket_list.html +++ b/templates/helpdesk/ticket_list.html @@ -24,6 +24,8 @@ $(document).ready(function() { {% for s in status_choices %} {{ s.1 }}{% endfor %} + + @@ -36,7 +38,7 @@ $(document).ready(function() { {{ ticket.title }} {{ ticket.queue }} {{ ticket.get_status_display }} -{{ ticket.created }} +{{ ticket.created|timesince }} ago {{ ticket.get_assigned_to }} {% endfor %}{% else %} diff --git a/urls.py b/urls.py index f55ce3da..765ef38d 100644 --- a/urls.py +++ b/urls.py @@ -48,7 +48,7 @@ urlpatterns = patterns('helpdesk.views', url(r'^tickets/(?P[0-9]+)/update/$', 'update_ticket', - name='helpdesk_view'), + name='helpdesk_update'), ) urlpatterns += patterns('', diff --git a/views.py b/views.py index 7528ce0a..d8b172ef 100644 --- a/views.py +++ b/views.py @@ -66,6 +66,14 @@ def view_ticket(request, ticket_id): if request.GET.has_key('take'): ticket.assigned_to = request.user ticket.save() + + if request.GET.has_key('close') and ticket.status == Ticket.RESOLVED_STATUS: + if not ticket.assigned_to: + owner = 0 + else: + owner = ticket.assigned_to.id + request.POST = {'new_status': Ticket.CLOSED_STATUS, 'public': 1, 'owner': owner, 'title': ticket.title, 'comment': "Accepted resolution and closed ticket"} + return update_ticket(request, ticket_id) return render_to_response('helpdesk/ticket.html', RequestContext(request, { @@ -120,6 +128,24 @@ def update_ticket(request, ticket_id): c.save() ticket.title = title + if f.new_status == Ticket.RESOLVED_STATUS: + ticket.resolution = comment + + if public and ticket.submitter_email: + context = { + 'ticket': ticket, + 'queue': ticket.queue, + 'resolution': ticket.resolution, + 'comment': f.comment, + } + if f.new_status == Ticket.RESOLVED_STATUS: + template = 'helpdesk/emails/submitter_resolved' + subject = '%s Ticket Resolved' + else: + template = 'helpdesk/emails/submitter_updated' + subject = '%s Ticket Updated' + send_multipart_mail(template, context, subject, ticket.submitter_email, ticket.queue.from_address) + ticket.save() return HttpResponseRedirect(ticket.get_absolute_url()) @@ -147,6 +173,19 @@ def ticket_list(request): statuses = [int(s) for s in statuses] tickets = tickets.filter(status__in=statuses) context = dict(context, statuses=statuses) + + ### KEYWORD SEARCHING + q = request.GET.get('q', None) + + if q: + qset = ( + Q(title__icontains=q) | + Q(description__icontains=q) | + Q(resolution__icontains=q) | + Q(submitter_email__icontains=q) + ) + tickets = tickets.filter(qset) + context = dict(context, query=q) ### SORTING sort = request.GET.get('sort', None)