From f419d8e2d009d1f2f2fd242e06cec5a540049a96 Mon Sep 17 00:00:00 2001 From: Ross Poulton Date: Wed, 9 Sep 2009 08:47:48 +0000 Subject: [PATCH] Issue #102 Add rudimentary CC: functionality on tickets, controlled by staff users. CC's can be e-mail addresses or users, who will receive copies of all emails sent to the Submitter. This is a work in progress. --- CHANGELOG | 5 ++ forms.py | 7 ++- models.py | 57 +++++++++++++++++++- templates/helpdesk/email_ignore_del.html | 2 +- templates/helpdesk/ticket.html | 11 ++-- templates/helpdesk/ticket_cc_add.html | 25 +++++++++ templates/helpdesk/ticket_cc_del.html | 14 +++++ templates/helpdesk/ticket_cc_list.html | 31 +++++++++++ urls.py | 12 +++++ views/api.py | 23 +++++++++ views/staff.py | 66 +++++++++++++++++++++++- 11 files changed, 245 insertions(+), 8 deletions(-) create mode 100644 templates/helpdesk/ticket_cc_add.html create mode 100644 templates/helpdesk/ticket_cc_del.html create mode 100644 templates/helpdesk/ticket_cc_list.html diff --git a/CHANGELOG b/CHANGELOG index 31b1ea1d..6f63438c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1 +1,6 @@ 2009-09-09 r138 Issue #104 Add a CHANGELOG file + +2009-09-09 r139 Issue #102 Add Ticket CC's +Add rudimentary CC: functionality on tickets, controlled by staff users. CC's +can be e-mail addresses or users, who will receive copies of all emails sent +to the Submitter. This is a work in progress. diff --git a/forms.py b/forms.py index 1e842147..bf533568 100644 --- a/forms.py +++ b/forms.py @@ -15,7 +15,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, Attachment, IgnoreEmail +from helpdesk.models import Ticket, Queue, FollowUp, Attachment, IgnoreEmail, TicketCC class EditTicketForm(forms.ModelForm): class Meta: @@ -356,3 +356,8 @@ class UserSettingsForm(forms.Form): class EmailIgnoreForm(forms.ModelForm): class Meta: model = IgnoreEmail + +class TicketCCForm(forms.ModelForm): + class Meta: + model = TicketCC + exclude = ('ticket',) diff --git a/models.py b/models.py index 72b285f6..2ec8da2d 100644 --- a/models.py +++ b/models.py @@ -129,7 +129,7 @@ class Queue(models.Model): help_text=_('Whether to use SSL for IMAP or POP3 - the default ports ' 'when using SSL are 993 for IMAP and 995 for POP3.'), ) - + email_box_user = models.CharField( _('E-Mail Username'), max_length=200, @@ -999,3 +999,58 @@ class IgnoreEmail(models.Model): return True else: return False + +class TicketCC(models.Model): + """ + Often, there are people who wish to follow a ticket who aren't the + person who originally submitted it. This model provides a way for those + people to follow a ticket. + + In this circumstance, a 'person' could be either an e-mail address or + an existing system user. + """ + + ticket = models.ForeignKey(Ticket) + + user = models.ForeignKey( + User, + blank=True, + null=True, + help_text=_('User who wishes to receive updates for this ticket.'), + ) + + email = models.EmailField( + _('E-Mail Address'), + blank=True, + null=True, + help_text=_('For non-user followers, enter their e-mail address'), + ) + + can_view = models.BooleanField( + _('Can View Ticket?'), + blank=True, + help_text=_('Can this CC login to view the ticket details?'), + ) + + can_update = models.BooleanField( + _('Can Update Ticket?'), + blank=True, + help_text=_('Can this CC login and update the ticket?'), + ) + + def _email_address(self): + if self.user and self.user.email is not None: + return self.user.email + else: + return self.email + email_address = property(_email_address) + + def _display(self): + if self.user: + return self.user + else: + return self.email + display = property(_display) + + def __unicode__(self): + return u'%s for %s' % (self.display, self.ticket.title) diff --git a/templates/helpdesk/email_ignore_del.html b/templates/helpdesk/email_ignore_del.html index 0ce564c6..93268933 100644 --- a/templates/helpdesk/email_ignore_del.html +++ b/templates/helpdesk/email_ignore_del.html @@ -5,7 +5,7 @@ {% 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. +

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

diff --git a/templates/helpdesk/ticket.html b/templates/helpdesk/ticket.html index 5fa6325f..eb2f8366 100644 --- a/templates/helpdesk/ticket.html +++ b/templates/helpdesk/ticket.html @@ -67,16 +67,21 @@ + {% trans "Copies To" %} + {% for ticketcc in ticket.ticketcc_set.all %}{{ ticketcc.display }}{% if not forloop.last %}, {% endif %}{% endfor %} {% trans "Manage" %} + + + {% trans "Description" %} - + {{ ticket.description|force_escape|linebreaksbr }} -{% if ticket.resolution %} +{% if ticket.resolution %} {% trans "Resolution" %}{% ifequal ticket.get_status_display "Resolved" %} {% trans "Accept" %}{% endifequal %} - + {{ ticket.resolution|force_escape }} {% endif %} diff --git a/templates/helpdesk/ticket_cc_add.html b/templates/helpdesk/ticket_cc_add.html new file mode 100644 index 00000000..80b9736b --- /dev/null +++ b/templates/helpdesk/ticket_cc_add.html @@ -0,0 +1,25 @@ +{% extends "helpdesk/base.html" %}{% load i18n %} + +{% block helpdesk_title %}{% trans "Add Ticket CC" %}{% endblock %} + +{% block helpdesk_body %}{% blocktrans %} +

Add Ticket CC

+ +

To automatically send an email to a user or e-mail address when this ticket is updated, select the user or enter an e-mail address below.

{% 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/ticket_cc_del.html b/templates/helpdesk/ticket_cc_del.html new file mode 100644 index 00000000..4aa0bb81 --- /dev/null +++ b/templates/helpdesk/ticket_cc_del.html @@ -0,0 +1,14 @@ +{% extends "helpdesk/base.html" %}{% load i18n %} + +{% block helpdesk_title %}{% trans "Delete Ticket CC" %}{% endblock %} + +{% block helpdesk_body %}{% blocktrans with cc.email_address as email_address %} +

Delete Ticket CC

+ +

Are you sure you wish to delete this email address ({{ email_address }}) from the CC list for this ticket? They will stop receiving updates.

+{% endblocktrans %} + +{% blocktrans %}

Don't Delete

+ +
+{% endblocktrans %}{% endblock %} diff --git a/templates/helpdesk/ticket_cc_list.html b/templates/helpdesk/ticket_cc_list.html new file mode 100644 index 00000000..27d7b381 --- /dev/null +++ b/templates/helpdesk/ticket_cc_list.html @@ -0,0 +1,31 @@ +{% extends "helpdesk/base.html" %}{% load i18n %} + +{% block helpdesk_title %}{% trans "Ticket CC Settings" %}{% endblock %} + +{% block helpdesk_body %}{% blocktrans with ticket.title as ticket_title and ticket.id as ticket_id %} +

Ticket CC Settings

+ +

The following people will receive an e-mail whenever {{ ticket_title }} is updated. Some people can also view or edit the ticket via the public ticket views.

+ +

You can add a new e-mail address to the list or delete any of the items below as required.

{% endblocktrans %} + + + + + + + +{% for person in copies_to %} + + + + + + +{% endfor %} + +
{% trans "Ticket CC List" %}
{% trans "E-Mail Address" %}{% trans "View?" %}{% trans "Update?" %}{% trans "Delete" %}
{{ person.display }}{{ person.can_view }}{{ person.can_update }}{% trans "Delete" %}
+ +

{% blocktrans with ticket.title as ticket_title %}Return to {{ ticket_title }}{% endblocktrans %}

+ +{% endblock %} diff --git a/urls.py b/urls.py index 162c4086..3a893d0c 100644 --- a/urls.py +++ b/urls.py @@ -55,6 +55,18 @@ urlpatterns = patterns('helpdesk.views.staff', 'unhold_ticket', name='helpdesk_unhold'), + url(r'^tickets/(?P[0-9]+)/cc/$', + 'ticket_cc', + name='helpdesk_ticket_cc'), + + url(r'^tickets/(?P[0-9]+)/cc/add/$', + 'ticket_cc_add', + name='helpdesk_ticket_cc_add'), + + url(r'^tickets/(?P[0-9]+)/cc/delete/(?P[0-9]+)/$', + 'ticket_cc_del', + name='helpdesk_ticket_cc_del'), + url(r'^raw/(?P\w+)/$', 'raw_details', name='helpdesk_raw'), diff --git a/views/api.py b/views/api.py index 017c51ba..92eeacff 100644 --- a/views/api.py +++ b/views/api.py @@ -209,6 +209,18 @@ class API: ) messages_sent_to.append(ticket.submitter_email) + if public: + for cc in ticket.ticketcc_set.all(): + if cc.email_address not in messages_sent_to: + send_templated_mail( + 'updated_submitter', + context, + recipients=cc.email_address, + sender=ticket.queue.from_address, + fail_silently=True, + ) + messages_sent_to.append(cc.email_address) + if ticket.queue.updated_ticket_cc and ticket.queue.updated_ticket_cc not in messages_sent_to: send_templated_mail( 'updated_cc', @@ -274,6 +286,17 @@ class API: ) messages_sent_to.append(ticket.submitter_email) + for cc in ticket.ticketcc_set.all(): + if cc.email_address not in messages_sent_to: + send_templated_mail( + 'resolved_submitter', + context, + recipients=cc.email_address, + sender=ticket.queue.from_address, + fail_silently=True, + ) + messages_sent_to.append(cc.email_address) + if ticket.queue.updated_ticket_cc and ticket.queue.updated_ticket_cc not in messages_sent_to: send_templated_mail( 'resolved_cc', diff --git a/views/staff.py b/views/staff.py index 5e43b973..8f181713 100644 --- a/views/staff.py +++ b/views/staff.py @@ -22,9 +22,9 @@ 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, EmailIgnoreForm, EditTicketForm +from helpdesk.forms import TicketForm, UserSettingsForm, EmailIgnoreForm, EditTicketForm, TicketCCForm 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, IgnoreEmail +from helpdesk.models import Ticket, Queue, FollowUp, TicketChange, PreSetReply, Attachment, SavedSearch, IgnoreEmail, TicketCC staff_member_required = user_passes_test(lambda u: u.is_authenticated() and u.is_active and u.is_staff) @@ -259,6 +259,17 @@ def update_ticket(request, ticket_id, public=False): ) messages_sent_to.append(ticket.submitter_email) + for cc in ticket.ticketcc_set.all(): + if cc.email_address not in messages_sent_to: + send_templated_mail( + template, + context, + recipients=cc.email_address, + sender=ticket.queue.from_address, + fail_silently=True, + ) + messages_sent_to.append(cc.email_address) + if ticket.assigned_to and request.user != ticket.assigned_to and ticket.assigned_to.email and ticket.assigned_to.email not in messages_sent_to: # We only send e-mails to staff members if the ticket is updated by # another user. The actual template varies, depending on what has been @@ -364,6 +375,17 @@ def mass_update(request): ) messages_sent_to.append(t.submitter_email) + for cc in ticket.ticketcc_set.all(): + if cc.email_address not in messages_sent_to: + send_templated_mail( + 'closed_submitter', + context, + recipients=cc.email_address, + sender=ticket.queue.from_address, + fail_silently=True, + ) + messages_sent_to.append(cc.email_address) + if t.assigned_to and request.user != t.assigned_to and t.assigned_to.email and t.assigned_to.email not in messages_sent_to: send_templated_mail( 'closed_owner', @@ -563,6 +585,7 @@ def edit_ticket(request, ticket_id): RequestContext(request, { 'form': form, })) +edit_ticket = staff_member_required(edit_ticket) def create_ticket(request): if request.method == 'POST': @@ -890,3 +913,42 @@ def email_ignore_del(request, id): 'ignore': ignore, })) email_ignore_del = superuser_required(email_ignore_del) + +def ticket_cc(request, ticket_id): + ticket = get_object_or_404(Ticket, id=ticket_id) + copies_to = ticket.ticketcc_set.all() + return render_to_response('helpdesk/ticket_cc_list.html', + RequestContext(request, { + 'copies_to': copies_to, + 'ticket': ticket, + })) +ticket_cc = staff_member_required(ticket_cc) + +def ticket_cc_add(request, ticket_id): + ticket = get_object_or_404(Ticket, id=ticket_id) + if request.method == 'POST': + form = TicketCCForm(request.POST) + if form.is_valid(): + ticketcc = form.save(commit=False) + ticketcc.ticket = ticket + ticketcc.save() + return HttpResponseRedirect(reverse('helpdesk_ticket_cc', kwargs={'ticket_id': ticket.id})) + else: + form = TicketCCForm() + return render_to_response('helpdesk/ticket_cc_add.html', + RequestContext(request, { + 'ticket': ticket, + 'form': form, + })) +ticket_cc_add = staff_member_required(ticket_cc_add) + +def ticket_cc_del(request, ticket_id, cc_id): + cc = get_object_or_404(TicketCC, ticket__id=ticket_id, id=cc_id) + if request.POST: + cc.delete() + return HttpResponseRedirect(reverse('helpdesk_ticket_cc', kwargs={'ticket_id': cc.ticket.id})) + return render_to_response('helpdesk/ticket_cc_del.html', + RequestContext(request, { + 'cc': cc, + })) +ticket_cc_del = staff_member_required(ticket_cc_del)