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.
This commit is contained in:
Ross Poulton 2009-09-09 08:47:48 +00:00
parent a9d5bb67a3
commit f419d8e2d0
11 changed files with 245 additions and 8 deletions

View File

@ -1 +1,6 @@
2009-09-09 r138 Issue #104 Add a CHANGELOG file 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.

View File

@ -15,7 +15,7 @@ from django.contrib.auth.models import User
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from helpdesk.lib import send_templated_mail 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 EditTicketForm(forms.ModelForm):
class Meta: class Meta:
@ -356,3 +356,8 @@ class UserSettingsForm(forms.Form):
class EmailIgnoreForm(forms.ModelForm): class EmailIgnoreForm(forms.ModelForm):
class Meta: class Meta:
model = IgnoreEmail model = IgnoreEmail
class TicketCCForm(forms.ModelForm):
class Meta:
model = TicketCC
exclude = ('ticket',)

View File

@ -129,7 +129,7 @@ class Queue(models.Model):
help_text=_('Whether to use SSL for IMAP or POP3 - the default ports ' help_text=_('Whether to use SSL for IMAP or POP3 - the default ports '
'when using SSL are 993 for IMAP and 995 for POP3.'), 'when using SSL are 993 for IMAP and 995 for POP3.'),
) )
email_box_user = models.CharField( email_box_user = models.CharField(
_('E-Mail Username'), _('E-Mail Username'),
max_length=200, max_length=200,
@ -999,3 +999,58 @@ class IgnoreEmail(models.Model):
return True return True
else: else:
return False 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)

View File

@ -5,7 +5,7 @@
{% block helpdesk_body %}{% blocktrans with ignore.email_address as email_address %} {% block helpdesk_body %}{% blocktrans with ignore.email_address as email_address %}
<h2>Un-Ignore E-Mail Address</h2> <h2>Un-Ignore E-Mail Address</h2>
<p>Are you sure you wish to stop removing this email address (<em>{{ email_address }}</em>) and allow their e-mails to automatically create tickets in your system? You can re-add this e-mail address at any time.<?p> <p>Are you sure you wish to stop removing this email address (<em>{{ email_address }}</em>) and allow their e-mails to automatically create tickets in your system? You can re-add this e-mail address at any time.</p>
{% endblocktrans %} {% endblocktrans %}
{% blocktrans %}<p><a href='../../'>Keep Ignoring It</a></p> {% blocktrans %}<p><a href='../../'>Keep Ignoring It</a></p>

View File

@ -67,16 +67,21 @@
</tr> </tr>
<tr class='row_odd'> <tr class='row_odd'>
<th>{% trans "Copies To" %}</th>
<td>{% for ticketcc in ticket.ticketcc_set.all %}{{ ticketcc.display }}{% if not forloop.last %}, {% endif %}{% endfor %} <strong><a href='{% url helpdesk_ticket_cc ticket.id %}'>{% trans "Manage" %}</a></strong></td>
</tr>
<tr class='row_even'>
<th colspan='2'>{% trans "Description" %}</th> <th colspan='2'>{% trans "Description" %}</th>
</tr> </tr>
<tr class='row_even'> <tr class='row_odd'>
<td colspan='2'>{{ ticket.description|force_escape|linebreaksbr }}</td> <td colspan='2'>{{ ticket.description|force_escape|linebreaksbr }}</td>
</tr> </tr>
{% if ticket.resolution %}<tr class='row_odd'> {% if ticket.resolution %}<tr class='row_even'>
<th colspan='2'>{% trans "Resolution" %}{% ifequal ticket.get_status_display "Resolved" %} <a href='?close'><img src='{{ MEDIA_URL }}helpdesk/buttons/accept.png' alt='{% trans "Accept" %}' title='{% trans "Accept and Close" %}' width='60' height='15' /></a>{% endifequal %}</th> <th colspan='2'>{% trans "Resolution" %}{% ifequal ticket.get_status_display "Resolved" %} <a href='?close'><img src='{{ MEDIA_URL }}helpdesk/buttons/accept.png' alt='{% trans "Accept" %}' title='{% trans "Accept and Close" %}' width='60' height='15' /></a>{% endifequal %}</th>
</tr> </tr>
<tr class='row_even'> <tr class='row_odd'>
<td colspan='2'>{{ ticket.resolution|force_escape }}</td> <td colspan='2'>{{ ticket.resolution|force_escape }}</td>
</tr>{% endif %} </tr>{% endif %}

View File

@ -0,0 +1,25 @@
{% extends "helpdesk/base.html" %}{% load i18n %}
{% block helpdesk_title %}{% trans "Add Ticket CC" %}{% endblock %}
{% block helpdesk_body %}{% blocktrans %}
<h2>Add Ticket CC</h2>
<p>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.</p>{% endblocktrans %}
<form method='post' action='./'>
<fieldset>
<dl>{% for field in form %}
<dt><label for='id_{{ field.name }}'>{{ field.label }}</label></dt>
<dd>{{ field }}</dd>
{% if field.errors %}<dd class='error'>{{ field.errors }}</dd>{% endif %}
{% if field.help_text %}<dd class='form_help_text'>{{ field.help_text }}</dd>{% endif %}
{% endfor %}</dl>
</fieldset>
<input type='submit' value='{% trans "Save Ticket CC" %}' />
</form>
{% endblock %}

View File

@ -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 %}
<h2>Delete Ticket CC</h2>
<p>Are you sure you wish to delete this email address (<em>{{ email_address }}</em>) from the CC list for this ticket? They will stop receiving updates.</p>
{% endblocktrans %}
{% blocktrans %}<p><a href='../../'>Don't Delete</a></p>
<form method='post' action='./'><input type='submit' value='Yes, Delete' /></form>
{% endblocktrans %}{% endblock %}

View File

@ -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 %}
<h2>Ticket CC Settings</h2>
<p>The following people will receive an e-mail whenever <em><a href='../'>{{ ticket_title }}</a></em> is updated. Some people can also view or edit the ticket via the public ticket views.</p>
<p>You can <a href='add/'>add a new e-mail address to the list</a> or delete any of the items below as required.</p>{% endblocktrans %}
<table width='100%'>
<thead>
<tr class='row_tablehead'><td colspan='4'>{% trans "Ticket CC List" %}</td></tr>
<tr class='row_columnheads'><th>{% trans "E-Mail Address" %}</th><th>{% trans "View?" %}</th><th>{% trans "Update?" %}</th><th>{% trans "Delete" %}</th></tr>
</thead>
<tbody>
{% for person in copies_to %}
<tr class='row_{% cycle odd,even %}'>
<td>{{ person.display }}</td>
<td>{{ person.can_view }}</td>
<td>{{ person.can_update }}</td>
<td><a href='{% url helpdesk_ticket_cc_del ticket.id person.id %}'>{% trans "Delete" %}</a></td>
</tr>
{% endfor %}
</tbody>
</table>
<p><a href='{% url helpdesk_view ticket.id %}'>{% blocktrans with ticket.title as ticket_title %}Return to <em>{{ ticket_title }}</em>{% endblocktrans %}</a></p>
{% endblock %}

12
urls.py
View File

@ -55,6 +55,18 @@ urlpatterns = patterns('helpdesk.views.staff',
'unhold_ticket', 'unhold_ticket',
name='helpdesk_unhold'), name='helpdesk_unhold'),
url(r'^tickets/(?P<ticket_id>[0-9]+)/cc/$',
'ticket_cc',
name='helpdesk_ticket_cc'),
url(r'^tickets/(?P<ticket_id>[0-9]+)/cc/add/$',
'ticket_cc_add',
name='helpdesk_ticket_cc_add'),
url(r'^tickets/(?P<ticket_id>[0-9]+)/cc/delete/(?P<cc_id>[0-9]+)/$',
'ticket_cc_del',
name='helpdesk_ticket_cc_del'),
url(r'^raw/(?P<type>\w+)/$', url(r'^raw/(?P<type>\w+)/$',
'raw_details', 'raw_details',
name='helpdesk_raw'), name='helpdesk_raw'),

View File

@ -209,6 +209,18 @@ class API:
) )
messages_sent_to.append(ticket.submitter_email) 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: if ticket.queue.updated_ticket_cc and ticket.queue.updated_ticket_cc not in messages_sent_to:
send_templated_mail( send_templated_mail(
'updated_cc', 'updated_cc',
@ -274,6 +286,17 @@ class API:
) )
messages_sent_to.append(ticket.submitter_email) 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: if ticket.queue.updated_ticket_cc and ticket.queue.updated_ticket_cc not in messages_sent_to:
send_templated_mail( send_templated_mail(
'resolved_cc', 'resolved_cc',

View File

@ -22,9 +22,9 @@ from django.shortcuts import render_to_response, get_object_or_404
from django.template import loader, Context, RequestContext from django.template import loader, Context, RequestContext
from django.utils.translation import ugettext as _ 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.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) 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) 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: 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 # 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 # 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) 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: 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( send_templated_mail(
'closed_owner', 'closed_owner',
@ -563,6 +585,7 @@ def edit_ticket(request, ticket_id):
RequestContext(request, { RequestContext(request, {
'form': form, 'form': form,
})) }))
edit_ticket = staff_member_required(edit_ticket)
def create_ticket(request): def create_ticket(request):
if request.method == 'POST': if request.method == 'POST':
@ -890,3 +913,42 @@ def email_ignore_del(request, id):
'ignore': ignore, 'ignore': ignore,
})) }))
email_ignore_del = superuser_required(email_ignore_del) 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)