diff --git a/forms.py b/forms.py index be8cfc77..e042a9ee 100644 --- a/forms.py +++ b/forms.py @@ -14,7 +14,7 @@ from django.contrib.auth.models import User from django.utils.translation import ugettext as _ from helpdesk.lib import send_templated_mail -from helpdesk.models import Ticket, Queue, FollowUp +from helpdesk.models import Ticket, Queue, FollowUp, IgnoreEmail class TicketForm(forms.Form): queue = forms.ChoiceField( @@ -265,3 +265,7 @@ class UserSettingsForm(forms.Form): help_text=_('If a ticket is altered by the API, do you want to receive an e-mail?'), required=False, ) + +class EmailIgnoreForm(forms.ModelForm): + class Meta: + model = IgnoreEmail diff --git a/lib.py b/lib.py index 5ed56631..0d64e1a0 100644 --- a/lib.py +++ b/lib.py @@ -165,8 +165,8 @@ def line_chart(data): max = field - # Set width to '65px * number of months'. - chart_url = 'http://chart.apis.google.com/chart?cht=lc&chs=%sx90&chd=t:' % (len(column_headings)*65) + # Set width to '65px * number of months + 100 for headings.'. + chart_url = 'http://chart.apis.google.com/chart?cht=lc&chs=%sx150&chd=t:' % (len(column_headings)*65+100) first_row = True row_headings = [] for row in data[1:]: @@ -210,8 +210,8 @@ def bar_chart(data): max = field - # Set width to '150px * number of months'. - chart_url = 'http://chart.apis.google.com/chart?cht=bvg&chs=%sx90&chd=t:' % (len(column_headings) * 150) + # Set width to '220px * number of months'. + chart_url = 'http://chart.apis.google.com/chart?cht=bvg&chs=%sx150&chd=t:' % (len(column_headings) * 220) first_row = True row_headings = [] for row in data[1:]: @@ -276,6 +276,8 @@ def apply_query(queryset, params): queryset = queryset.filter(params['other_filter']) if params.get('sorting', None): + if params.get('sortreverse', None): + params['sorting'] = "-%s" % params['sorting'] queryset = queryset.order_by(params['sorting']) return queryset diff --git a/management/commands/get_email.py b/management/commands/get_email.py index 29d82564..3658c91b 100644 --- a/management/commands/get_email.py +++ b/management/commands/get_email.py @@ -20,10 +20,11 @@ from email.Utils import parseaddr from django.core.files.base import ContentFile from django.core.management.base import BaseCommand +from django.db.models import Q from django.utils.translation import ugettext as _ from helpdesk.lib import send_templated_mail -from helpdesk.models import Queue, Ticket, FollowUp, Attachment +from helpdesk.models import Queue, Ticket, FollowUp, Attachment, IgnoreEmail class Command(BaseCommand): @@ -76,9 +77,11 @@ def process_queue(q): msgSize = msg.split(" ")[1] full_message = "\n".join(server.retr(msgNum)[1]) - ticket_from_message(message=full_message, queue=q) + ticket = ticket_from_message(message=full_message, queue=q) + + if ticket: + server.dele(msgNum) - server.dele(msgNum) server.quit() elif q.email_box_type == 'imap': @@ -94,8 +97,10 @@ def process_queue(q): 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') + ticket = ticket_from_message(message=data[0][1], queue=q) + if ticket: + server.store(num, '+FLAGS', '\\Deleted') + server.expunge() server.close() server.logout() @@ -111,8 +116,10 @@ def ticket_from_message(message, queue): sender = message.get('from', _('Unknown Sender')) sender_email = parseaddr(sender)[1] - if sender_email.startswith('postmaster'): - sender_email = '' + + for ignore in IgnoreEmail.objects.filter(Q(queues=queue) | Q(queues__isnull=True)): + if ignore.test(sender_email): + return False regex = re.compile("^\[[A-Za-z0-9]+-\d+\]") if regex.match(subject): @@ -256,6 +263,8 @@ def ticket_from_message(message, queue): a.save() print " - %s" % file['filename'] + return ticket + if __name__ == '__main__': process_email() diff --git a/models.py b/models.py index decdc4a2..430dc3ba 100644 --- a/models.py +++ b/models.py @@ -926,3 +926,68 @@ def create_usersettings(sender, created_models=[], instance=None, created=False, models.signals.post_syncdb.connect(create_usersettings) models.signals.post_save.connect(create_usersettings, sender=User) + +class IgnoreEmail(models.Model): + """ + This model lets us easily ignore e-mails from certain senders when + processing IMAP and POP3 mailboxes, eg mails from postmaster or from + known trouble-makers. + """ + queues = models.ManyToManyField( + Queue, + blank=True, + null=True, + help_text=_('Leave blank for this e-mail to be ignored on all ' + 'queues, or select those queues you wish to ignore this e-mail ' + 'for.'), + ) + + name = models.CharField( + _('Name'), + max_length=100, + ) + + date = models.DateField( + _('Date'), + help_text=_('Date on which this e-mail address was added'), + blank=True, + editable=False + ) + + email_address = models.CharField( + _('E-Mail Address'), + max_length=150, + help_text=_('Enter a full e-mail address, or portions with ' + 'wildcards, eg *@domain.com or postmaster@*.'), + ) + + def __unicode__(self): + return u'%s' % self.name + + def save(self): + if not self.date: + self.date = datetime.now() + return super(IgnoreEmail, self).save() + + def test(self, email): + """ + Possible situations: + 1. Username & Domain both match + 2. Username is wildcard, domain matches + 3. Username matches, domain is wildcard + 4. username & domain are both wildcards + 5. Other (no match) + + 1-4 return True, 5 returns False. + """ + + own_parts = self.email_address.split("@") + email_parts = email.split("@") + + if self.email_address == email \ + or own_parts[0] == "*" and own_parts[1] == email_parts[1] \ + or own_parts[1] == "*" and own_parts[0] == email_parts[0] \ + or own_parts[0] == "*" and own_parts[1] == "*": + return True + else: + return False diff --git a/templates/helpdesk/base.html b/templates/helpdesk/base.html index f45a45a2..5c7fd446 100644 --- a/templates/helpdesk/base.html +++ b/templates/helpdesk/base.html @@ -28,7 +28,7 @@ {% block helpdesk_body %}{% endblock %} {% include "helpdesk/debug.html" %} diff --git a/templates/helpdesk/dashboard.html b/templates/helpdesk/dashboard.html index 529e76c8..053a6594 100644 --- a/templates/helpdesk/dashboard.html +++ b/templates/helpdesk/dashboard.html @@ -5,7 +5,7 @@ {% endblock %} {% block helpdesk_body %} - +
{% for queue in dash_tickets %} @@ -17,7 +17,7 @@ {% endfor %}
{% trans "Helpdesk Summary" %}
{% trans "Queue" %}{% trans "Open" %}{% trans "Resolved" %}
-

Welcome to your Helpdesk Dashboard! From here you can quickly see your own tickets, and those tickets that have no owner. Why not pick up an orphan ticket and sort it out for a customer?

+

Welcome to your Helpdesk Dashboard! From here you can quickly see your own tickets, and those tickets that have no owner. Why not pick up an orphan ticket and sort it out for a customer?


@@ -34,6 +34,9 @@ {{ ticket.modified|timesince }} {% endfor %} +{% if not unassigned_tickets %} +{% trans "You have no tickets assigned to you." %} +{% endif %} @@ -49,6 +52,9 @@ {% endfor %} +{% if not unassigned_tickets %} + +{% endif %}
{% trans "Take" %}
{% trans "There are no unassigned tickets." %}
{% endblock %} diff --git a/templates/helpdesk/email_ignore_add.html b/templates/helpdesk/email_ignore_add.html new file mode 100644 index 00000000..e79888de --- /dev/null +++ b/templates/helpdesk/email_ignore_add.html @@ -0,0 +1,27 @@ +{% extends "helpdesk/base.html" %}{% load i18n %} + +{% block helpdesk_title %}{% trans "Ignore E-Mail Address" %}{% endblock %} + +{% block helpdesk_body %}{% blocktrans %} +

Ignore E-Mail Address

+ +

To ignore an e-mail address and prevent any emails from that address creating tickets automatically, enter the e-mail address below.

+ +

You can either enter a whole e-mail address such as email@domain.com or a portion of an e-mail address with a wildcard, such as *@domain.com or user@*.

{% endblocktrans %} + +
+ +
+
{% for field in form %} +
+
{{ field }}
+ {% if field.errors %}
{{ field.errors }}
{% endif %} + {% if field.help_text %}
{{ field.help_text }}
{% endif %} + {% endfor %}
+
+ + + +
+ +{% endblock %} diff --git a/templates/helpdesk/email_ignore_del.html b/templates/helpdesk/email_ignore_del.html new file mode 100644 index 00000000..0ce564c6 --- /dev/null +++ b/templates/helpdesk/email_ignore_del.html @@ -0,0 +1,14 @@ +{% extends "helpdesk/base.html" %}{% load i18n %} + +{% block helpdesk_title %}{% trans "Delete Ignored E-Mail Address" %}{% endblock %} + +{% block helpdesk_body %}{% blocktrans with ignore.email_address as email_address %} +

Un-Ignore E-Mail Address

+ +

Are you sure you wish to stop removing this email address ({{ email_address }}) and allow their e-mails to automatically create tickets in your system? You can re-add this e-mail address at any time. +{% endblocktrans %} + +{% blocktrans %}

Keep Ignoring It

+ +
+{% endblocktrans %}{% endblock %} diff --git a/templates/helpdesk/email_ignore_list.html b/templates/helpdesk/email_ignore_list.html new file mode 100644 index 00000000..834c8d2e --- /dev/null +++ b/templates/helpdesk/email_ignore_list.html @@ -0,0 +1,28 @@ +{% extends "helpdesk/base.html" %}{% load i18n %} + +{% block helpdesk_title %}{% trans "Ignored E-Mail Addresses" %}{% endblock %} + +{% block helpdesk_body %}{% blocktrans %} +

Ignored E-Mail Addresses

+ +

The following e-mail addresses are currently being ignored by the incoming e-mail processor. You can add a new e-mail address to the list or delete any of the items below as required.

{% endblocktrans %} + + + + + + + +{% for ignore in ignore_list %} + + + + + + + +{% endfor %} + +
{% trans "Ignored E-Mail Addresses" %}
{% trans "Name" %}{% trans "E-Mail Address" %}{% trans "Date Added" %}{% trans "Queues" %}{% trans "Delete" %}
{{ ignore.name }}{{ ignore.email_address }}{{ ignore.date }}{% for queue in ignore.queues.all %}{{ queue.slug }}{% if not forloop.last %}, {% endif %}{% endfor %}{% if not ignore.queues.all %}All{% endif %}Delete
+ +{% endblock %} diff --git a/templates/helpdesk/report_output.html b/templates/helpdesk/report_output.html index a6358d0b..50ed6acb 100644 --- a/templates/helpdesk/report_output.html +++ b/templates/helpdesk/report_output.html @@ -6,8 +6,10 @@

{% trans "Reports & Statistics" %}

-{% for h in headings %}{% endfor %} -{% for d in data %}{% for f in d %}{% endfor %}{% endfor %} + +{% for h in headings %}{% endfor %} +{% for d in data %} +{% for f in d %}{% endfor %}{% endfor %}
{{ h }}
{{ f }}
{{ title }}
{% if forloop.first %}{{ h|title }}{% else %}{{ h }}{% endif %}
{{ f }}
{% if chart %}{% endif %} diff --git a/templates/helpdesk/system_settings.html b/templates/helpdesk/system_settings.html new file mode 100644 index 00000000..ea7d7a1a --- /dev/null +++ b/templates/helpdesk/system_settings.html @@ -0,0 +1,19 @@ +{% extends "helpdesk/base.html" %}{% load i18n %} + +{% block helpdesk_title %}{% trans "Change System Settings" %}{% endblock %} + +{% block helpdesk_body %}{% blocktrans %} +

System Settings

+ +

The following items can be maintained by you or other superusers:

{% endblocktrans %} + + +{% endblock %} diff --git a/templates/helpdesk/ticket.html b/templates/helpdesk/ticket.html index 1f40a95f..74cce06e 100644 --- a/templates/helpdesk/ticket.html +++ b/templates/helpdesk/ticket.html @@ -60,7 +60,7 @@ {% trans "Submitter E-Mail" %} - {{ ticket.submitter_email }} + {{ ticket.submitter_email }}{% if user.is_superuser %} {% trans "Ignore" %}{% endif %} diff --git a/templates/helpdesk/ticket_list.html b/templates/helpdesk/ticket_list.html index 2eefea24..d22c5748 100644 --- a/templates/helpdesk/ticket_list.html +++ b/templates/helpdesk/ticket_list.html @@ -43,6 +43,7 @@ $(document).ready(function() { +

Ordering applied to tickets

diff --git a/urls.py b/urls.py index 4fb802f1..10f862b4 100644 --- a/urls.py +++ b/urls.py @@ -73,6 +73,18 @@ urlpatterns = patterns('helpdesk.views.staff', url(r'^settings/$', 'user_settings', name='helpdesk_user_settings'), + + url(r'^ignore/$', + 'email_ignore', + name='helpdesk_email_ignore'), + + url(r'^ignore/add/$', + 'email_ignore_add', + name='helpdesk_email_ignore_add'), + + url(r'^ignore/delete/(?P[0-9]+)/$', + 'email_ignore_del', + name='helpdesk_email_ignore_del'), ) urlpatterns += patterns('helpdesk.views.public', @@ -129,4 +141,9 @@ urlpatterns += patterns('', 'django.views.generic.simple.direct_to_template', {'template': 'helpdesk/help_context.html',}, name='helpdesk_help_context'), + + url(r'^system_settings/$', + 'django.views.generic.simple.direct_to_template', + {'template': 'helpdesk/system_settings.html',}, + name='helpdesk_system_settings'), ) diff --git a/views/staff.py b/views/staff.py index 72cb40d9..447981c8 100644 --- a/views/staff.py +++ b/views/staff.py @@ -10,7 +10,7 @@ views/staff.py - The bulk of the application - provides most business logic and from datetime import datetime from django.contrib.auth.models import User -from django.contrib.auth.decorators import login_required +from django.contrib.auth.decorators import login_required, user_passes_test from django.core.files.base import ContentFile from django.core.urlresolvers import reverse from django.db import connection @@ -20,9 +20,14 @@ from django.shortcuts import render_to_response, get_object_or_404 from django.template import loader, Context, RequestContext from django.utils.translation import ugettext as _ -from helpdesk.forms import TicketForm, UserSettingsForm +from helpdesk.forms import TicketForm, UserSettingsForm, EmailIgnoreForm from helpdesk.lib import send_templated_mail, line_chart, bar_chart, query_to_dict, apply_query, safe_template_context -from helpdesk.models import Ticket, Queue, FollowUp, TicketChange, PreSetReply, Attachment, SavedSearch +from helpdesk.models import Ticket, Queue, FollowUp, TicketChange, PreSetReply, Attachment, SavedSearch, IgnoreEmail + + +staff_member_required = user_passes_test(lambda u: u.is_authenticated() and u.is_active and u.is_staff) +superuser_required = user_passes_test(lambda u: u.is_authenticated() and u.is_active and u.is_superuser) + def dashboard(request): @@ -199,7 +204,7 @@ def update_ticket(request, ticket_id): if f.new_status == Ticket.RESOLVED_STATUS: ticket.resolution = comment - if ticket.submitter_email and ((f.comment != '' and public) or (f.new_status in (Ticket.RESOLVED_STATUS, Ticket.CLOSED_STATUS))): + if ticket.submitter_email and public and (f.comment or (f.new_status in (Ticket.RESOLVED_STATUS, Ticket.CLOSED_STATUS))): context = { 'ticket': ticket, 'queue': ticket.queue, @@ -287,6 +292,7 @@ def ticket_list(request): query_params = { 'filtering': {}, 'sorting': None, + 'sortreverse': False, 'keyword': None, 'other_filter': None, } @@ -308,7 +314,8 @@ def ticket_list(request): or request.GET.has_key('assigned_to') or request.GET.has_key('status') or request.GET.has_key('q') - or request.GET.has_key('sort') ): + or request.GET.has_key('sort') + or request.GET.has_key('sortreverse') ): # Fall-back if no querying is being done, force the list to only # show open/reopened/resolved (not closed) cases sorted by creation @@ -353,6 +360,9 @@ def ticket_list(request): if sort not in ('status', 'assigned_to', 'created', 'title', 'queue', 'priority'): sort = 'created' query_params['sorting'] = sort + + sortreverse = request.GET.get('sortreverse', None) + query_params['sortreverse'] = sortreverse tickets = apply_query(Ticket.objects.select_related(), query_params) @@ -555,30 +565,37 @@ def run_report(request, report): if report == 'userpriority': sql = user_base_sql % priority_sql columns = ['username'] + priority_columns + title = 'User by Priority' elif report == 'userqueue': sql = user_base_sql % queue_sql columns = ['username'] + queue_columns + title = 'User by Queue' elif report == 'userstatus': sql = user_base_sql % status_sql columns = ['username'] + status_columns + title = 'User by Status' elif report == 'usermonth': sql = user_base_sql % month_sql columns = ['username'] + month_columns + title = 'User by Month' elif report == 'queuepriority': sql = queue_base_sql % priority_sql columns = ['queue'] + priority_columns + title = 'Queue by Priority' elif report == 'queuestatus': sql = queue_base_sql % status_sql columns = ['queue'] + status_columns + title = 'Queue by Status' elif report == 'queuemonth': sql = queue_base_sql % month_sql columns = ['queue'] + month_columns + title = 'Queue by Month' cursor = connection.cursor() @@ -604,8 +621,8 @@ def run_report(request, report): RequestContext(request, { 'headings': columns, 'data': data, - 'sql': sql, 'chart': chart_url, + 'title': title, })) run_report = login_required(run_report) @@ -638,6 +655,7 @@ def delete_saved_query(request, id): })) delete_saved_query = login_required(delete_saved_query) + def user_settings(request): s = request.user.usersettings if request.POST: @@ -653,3 +671,40 @@ def user_settings(request): 'form': form, })) user_settings = login_required(user_settings) + + +def email_ignore(request): + return render_to_response('helpdesk/email_ignore_list.html', + RequestContext(request, { + 'ignore_list': IgnoreEmail.objects.all(), + })) +email_ignore = superuser_required(email_ignore) + + +def email_ignore_add(request): + if request.method == 'POST': + form = EmailIgnoreForm(request.POST) + if form.is_valid(): + ignore = form.save() + return HttpResponseRedirect(reverse('helpdesk_email_ignore')) + else: + form = EmailIgnoreForm(request.GET) + + return render_to_response('helpdesk/email_ignore_add.html', + RequestContext(request, { + 'form': form, + })) +email_ignore_add = superuser_required(email_ignore_add) + + +def email_ignore_del(request, id): + ignore = get_object_or_404(IgnoreEmail, id=id) + if request.method == 'POST': + ignore.delete() + return HttpResponseRedirect(reverse('helpdesk_email_ignore')) + else: + return render_to_response('helpdesk/email_ignore_del.html', + RequestContext(request, { + 'ignore': ignore, + })) +email_ignore_del = superuser_required(email_ignore_del)